diff --git a/.changeset/better-ghosts-tell.md b/.changeset/better-ghosts-tell.md new file mode 100644 index 0000000000..af66b4fc6e --- /dev/null +++ b/.changeset/better-ghosts-tell.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +Fixes app background color on dark mode. diff --git a/.changeset/dependabot-aed8e01.md b/.changeset/dependabot-aed8e01.md new file mode 100644 index 0000000000..8606ea8b91 --- /dev/null +++ b/.changeset/dependabot-aed8e01.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +build(deps-dev): bump `storybook` from 10.1.9 to 10.1.10 diff --git a/.changeset/every-bears-add.md b/.changeset/every-bears-add.md new file mode 100644 index 0000000000..83c6d6b06d --- /dev/null +++ b/.changeset/every-bears-add.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Bump `@swc/core` to support `ES2023` and `ES2024` diff --git a/.changeset/flat-pillows-rush.md b/.changeset/flat-pillows-rush.md deleted file mode 100644 index bb64013f62..0000000000 --- a/.changeset/flat-pillows-rush.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-plugin-api': patch ---- - -Fixed a versioning conflict that could result in a `.withContext` is not a function error. diff --git a/.changeset/gentle-trains-juggle.md b/.changeset/gentle-trains-juggle.md deleted file mode 100644 index e8b1397180..0000000000 --- a/.changeset/gentle-trains-juggle.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-kubernetes-backend': patch ---- - -Replace `@aws-sdk/signature-v4` with `@smithy/signature-v4`, -as stated in the [package documentation](https://www.npmjs.com/package/@aws-sdk/signature-v4?activeTab=readme) diff --git a/.changeset/hot-colts-float.md b/.changeset/hot-colts-float.md new file mode 100644 index 0000000000..093dcfb12a --- /dev/null +++ b/.changeset/hot-colts-float.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Fixed SearchField `startCollapsed` prop not working correctly in Backstage UI. The field now properly starts in a collapsed state, expands when clicked and focused, and collapses back when unfocused with no input. Also fixed CSS logic to work correctly in all layout contexts (flex row, flex column, and regular containers). + +Affected components: SearchField diff --git a/.changeset/major-mangos-shine.md b/.changeset/major-mangos-shine.md new file mode 100644 index 0000000000..f42fcf5d0e --- /dev/null +++ b/.changeset/major-mangos-shine.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-openapi-utils': patch +--- + +Internal update to use native feature from our request validation library for handling base path determination. diff --git a/.changeset/pre.json b/.changeset/pre.json index c27ea0b35a..a985ff639f 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -2,208 +2,208 @@ "mode": "pre", "tag": "next", "initialVersions": { - "example-app": "0.2.115", - "@backstage/app-defaults": "1.7.2", - "example-app-next": "0.0.29", - "app-next-example-plugin": "0.0.29", - "example-backend": "0.0.44", - "@backstage/backend-app-api": "1.3.0", - "@backstage/backend-defaults": "0.13.1", - "@backstage/backend-dev-utils": "0.1.5", - "@backstage/backend-dynamic-feature-service": "0.7.6", - "@backstage/backend-openapi-utils": "0.6.3", - "@backstage/backend-plugin-api": "1.5.0", - "@backstage/backend-test-utils": "1.10.0", + "example-app": "0.2.116", + "@backstage/app-defaults": "1.7.3", + "example-app-next": "0.0.30", + "app-next-example-plugin": "0.0.30", + "example-backend": "0.0.45", + "@backstage/backend-app-api": "1.4.0", + "@backstage/backend-defaults": "0.14.0", + "@backstage/backend-dev-utils": "0.1.6", + "@backstage/backend-dynamic-feature-service": "0.7.7", + "@backstage/backend-openapi-utils": "0.6.4", + "@backstage/backend-plugin-api": "1.6.0", + "@backstage/backend-test-utils": "1.10.2", "@backstage/catalog-client": "1.12.1", "@backstage/catalog-model": "1.7.6", - "@backstage/cli": "0.34.5", - "@backstage/cli-common": "0.1.15", - "@backstage/cli-node": "0.2.15", - "@backstage/codemods": "0.1.52", + "@backstage/cli": "0.35.0", + "@backstage/cli-common": "0.1.16", + "@backstage/cli-node": "0.2.16", + "@backstage/codemods": "0.1.53", "@backstage/config": "1.3.6", - "@backstage/config-loader": "1.10.6", - "@backstage/core-app-api": "1.19.2", - "@backstage/core-compat-api": "0.5.4", - "@backstage/core-components": "0.18.3", - "@backstage/core-plugin-api": "1.12.0", - "@backstage/create-app": "0.7.6", - "@backstage/dev-utils": "1.1.17", - "e2e-test": "0.2.34", + "@backstage/config-loader": "1.10.7", + "@backstage/core-app-api": "1.19.3", + "@backstage/core-compat-api": "0.5.5", + "@backstage/core-components": "0.18.4", + "@backstage/core-plugin-api": "1.12.1", + "@backstage/create-app": "0.7.7", + "@backstage/dev-utils": "1.1.18", + "e2e-test": "0.2.35", "@backstage/e2e-test-utils": "0.1.1", "@backstage/errors": "1.2.7", "@backstage/eslint-plugin": "0.2.0", - "@backstage/frontend-app-api": "0.13.2", - "@backstage/frontend-defaults": "0.3.3", - "@backstage/frontend-dynamic-feature-loader": "0.1.7", - "@internal/frontend": "0.0.15", - "@backstage/frontend-plugin-api": "0.13.0", - "@backstage/frontend-test-utils": "0.4.1", - "@backstage/integration": "1.18.2", + "@backstage/frontend-app-api": "0.13.3", + "@backstage/frontend-defaults": "0.3.4", + "@backstage/frontend-dynamic-feature-loader": "0.1.8", + "@internal/frontend": "0.0.16", + "@backstage/frontend-plugin-api": "0.13.2", + "@backstage/frontend-test-utils": "0.4.2", + "@backstage/integration": "1.19.0", "@backstage/integration-aws-node": "0.1.19", - "@backstage/integration-react": "1.2.12", + "@backstage/integration-react": "1.2.13", "@internal/opaque": "0.0.1", "@backstage/release-manifests": "0.0.13", - "@backstage/repo-tools": "0.16.0", - "@internal/scaffolder": "0.0.15", - "@techdocs/cli": "1.10.2", - "techdocs-cli-embedded-app": "0.2.114", - "@backstage/test-utils": "1.7.13", - "@backstage/theme": "0.7.0", + "@backstage/repo-tools": "0.16.1", + "@internal/scaffolder": "0.0.16", + "@techdocs/cli": "1.10.3", + "techdocs-cli-embedded-app": "0.2.115", + "@backstage/test-utils": "1.7.14", + "@backstage/theme": "0.7.1", "@backstage/types": "1.2.2", - "@backstage/ui": "0.9.0", + "@backstage/ui": "0.10.0", "@backstage/version-bridge": "1.0.11", - "yarn-plugin-backstage": "0.0.7", - "@backstage/plugin-api-docs": "0.13.1", + "yarn-plugin-backstage": "0.0.8", + "@backstage/plugin-api-docs": "0.13.2", "@backstage/plugin-api-docs-module-protoc-gen-doc": "0.1.10", - "@backstage/plugin-app": "0.3.2", - "@backstage/plugin-app-backend": "0.5.8", - "@backstage/plugin-app-node": "0.1.39", - "@backstage/plugin-app-visualizer": "0.1.25", - "@backstage/plugin-auth": "0.1.2", - "@backstage/plugin-auth-backend": "0.25.6", - "@backstage/plugin-auth-backend-module-atlassian-provider": "0.4.9", - "@backstage/plugin-auth-backend-module-auth0-provider": "0.2.9", - "@backstage/plugin-auth-backend-module-aws-alb-provider": "0.4.9", - "@backstage/plugin-auth-backend-module-azure-easyauth-provider": "0.2.14", - "@backstage/plugin-auth-backend-module-bitbucket-provider": "0.3.9", - "@backstage/plugin-auth-backend-module-bitbucket-server-provider": "0.2.9", - "@backstage/plugin-auth-backend-module-cloudflare-access-provider": "0.4.9", - "@backstage/plugin-auth-backend-module-gcp-iap-provider": "0.4.9", - "@backstage/plugin-auth-backend-module-github-provider": "0.3.9", - "@backstage/plugin-auth-backend-module-gitlab-provider": "0.3.9", - "@backstage/plugin-auth-backend-module-google-provider": "0.3.9", - "@backstage/plugin-auth-backend-module-guest-provider": "0.2.14", - "@backstage/plugin-auth-backend-module-microsoft-provider": "0.3.9", - "@backstage/plugin-auth-backend-module-oauth2-provider": "0.4.9", - "@backstage/plugin-auth-backend-module-oauth2-proxy-provider": "0.2.14", - "@backstage/plugin-auth-backend-module-oidc-provider": "0.4.9", - "@backstage/plugin-auth-backend-module-okta-provider": "0.2.9", - "@backstage/plugin-auth-backend-module-onelogin-provider": "0.3.9", - "@backstage/plugin-auth-backend-module-openshift-provider": "0.1.2", - "@backstage/plugin-auth-backend-module-pinniped-provider": "0.3.9", - "@backstage/plugin-auth-backend-module-vmware-cloud-provider": "0.5.9", - "@backstage/plugin-auth-node": "0.6.9", - "@backstage/plugin-auth-react": "0.1.21", - "@backstage/plugin-bitbucket-cloud-common": "0.3.4", - "@backstage/plugin-catalog": "1.32.0", - "@backstage/plugin-catalog-backend": "3.2.0", - "@backstage/plugin-catalog-backend-module-aws": "0.4.17", - "@backstage/plugin-catalog-backend-module-azure": "0.3.11", - "@backstage/plugin-catalog-backend-module-backstage-openapi": "0.5.8", - "@backstage/plugin-catalog-backend-module-bitbucket-cloud": "0.5.5", - "@backstage/plugin-catalog-backend-module-bitbucket-server": "0.5.5", - "@backstage/plugin-catalog-backend-module-gcp": "0.3.14", - "@backstage/plugin-catalog-backend-module-gerrit": "0.3.8", - "@backstage/plugin-catalog-backend-module-gitea": "0.1.6", - "@backstage/plugin-catalog-backend-module-github": "0.11.2", - "@backstage/plugin-catalog-backend-module-github-org": "0.3.16", - "@backstage/plugin-catalog-backend-module-gitlab": "0.7.5", - "@backstage/plugin-catalog-backend-module-gitlab-org": "0.2.15", - "@backstage/plugin-catalog-backend-module-incremental-ingestion": "0.7.6", - "@backstage/plugin-catalog-backend-module-ldap": "0.12.0", - "@backstage/plugin-catalog-backend-module-logs": "0.1.16", - "@backstage/plugin-catalog-backend-module-msgraph": "0.8.2", - "@backstage/plugin-catalog-backend-module-openapi": "0.2.16", - "@backstage/plugin-catalog-backend-module-puppetdb": "0.2.16", - "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "0.2.14", - "@backstage/plugin-catalog-backend-module-unprocessed": "0.6.6", + "@backstage/plugin-app": "0.3.3", + "@backstage/plugin-app-backend": "0.5.9", + "@backstage/plugin-app-node": "0.1.40", + "@backstage/plugin-app-visualizer": "0.1.26", + "@backstage/plugin-auth": "0.1.3", + "@backstage/plugin-auth-backend": "0.25.7", + "@backstage/plugin-auth-backend-module-atlassian-provider": "0.4.10", + "@backstage/plugin-auth-backend-module-auth0-provider": "0.2.10", + "@backstage/plugin-auth-backend-module-aws-alb-provider": "0.4.10", + "@backstage/plugin-auth-backend-module-azure-easyauth-provider": "0.2.15", + "@backstage/plugin-auth-backend-module-bitbucket-provider": "0.3.10", + "@backstage/plugin-auth-backend-module-bitbucket-server-provider": "0.2.10", + "@backstage/plugin-auth-backend-module-cloudflare-access-provider": "0.4.10", + "@backstage/plugin-auth-backend-module-gcp-iap-provider": "0.4.10", + "@backstage/plugin-auth-backend-module-github-provider": "0.4.0", + "@backstage/plugin-auth-backend-module-gitlab-provider": "0.3.10", + "@backstage/plugin-auth-backend-module-google-provider": "0.3.10", + "@backstage/plugin-auth-backend-module-guest-provider": "0.2.15", + "@backstage/plugin-auth-backend-module-microsoft-provider": "0.3.10", + "@backstage/plugin-auth-backend-module-oauth2-provider": "0.4.10", + "@backstage/plugin-auth-backend-module-oauth2-proxy-provider": "0.2.15", + "@backstage/plugin-auth-backend-module-oidc-provider": "0.4.10", + "@backstage/plugin-auth-backend-module-okta-provider": "0.2.10", + "@backstage/plugin-auth-backend-module-onelogin-provider": "0.3.10", + "@backstage/plugin-auth-backend-module-openshift-provider": "0.1.3", + "@backstage/plugin-auth-backend-module-pinniped-provider": "0.3.10", + "@backstage/plugin-auth-backend-module-vmware-cloud-provider": "0.5.10", + "@backstage/plugin-auth-node": "0.6.10", + "@backstage/plugin-auth-react": "0.1.22", + "@backstage/plugin-bitbucket-cloud-common": "0.3.5", + "@backstage/plugin-catalog": "1.32.1", + "@backstage/plugin-catalog-backend": "3.3.0", + "@backstage/plugin-catalog-backend-module-aws": "0.4.18", + "@backstage/plugin-catalog-backend-module-azure": "0.3.12", + "@backstage/plugin-catalog-backend-module-backstage-openapi": "0.5.9", + "@backstage/plugin-catalog-backend-module-bitbucket-cloud": "0.5.6", + "@backstage/plugin-catalog-backend-module-bitbucket-server": "0.5.6", + "@backstage/plugin-catalog-backend-module-gcp": "0.3.15", + "@backstage/plugin-catalog-backend-module-gerrit": "0.3.9", + "@backstage/plugin-catalog-backend-module-gitea": "0.1.7", + "@backstage/plugin-catalog-backend-module-github": "0.12.0", + "@backstage/plugin-catalog-backend-module-github-org": "0.3.17", + "@backstage/plugin-catalog-backend-module-gitlab": "0.7.6", + "@backstage/plugin-catalog-backend-module-gitlab-org": "0.2.16", + "@backstage/plugin-catalog-backend-module-incremental-ingestion": "0.7.7", + "@backstage/plugin-catalog-backend-module-ldap": "0.12.1", + "@backstage/plugin-catalog-backend-module-logs": "0.1.17", + "@backstage/plugin-catalog-backend-module-msgraph": "0.8.3", + "@backstage/plugin-catalog-backend-module-openapi": "0.2.17", + "@backstage/plugin-catalog-backend-module-puppetdb": "0.2.17", + "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "0.2.15", + "@backstage/plugin-catalog-backend-module-unprocessed": "0.6.7", "@backstage/plugin-catalog-common": "1.1.7", - "@backstage/plugin-catalog-graph": "0.5.3", - "@backstage/plugin-catalog-import": "0.13.7", - "@backstage/plugin-catalog-node": "1.20.0", - "@backstage/plugin-catalog-react": "1.21.3", - "@backstage/plugin-catalog-unprocessed-entities": "0.2.23", - "@backstage/plugin-catalog-unprocessed-entities-common": "0.0.11", - "@backstage/plugin-config-schema": "0.1.74", - "@backstage/plugin-devtools": "0.1.33", - "@backstage/plugin-devtools-backend": "0.5.11", - "@backstage/plugin-devtools-common": "0.1.19", - "@backstage/plugin-events-backend": "0.5.8", - "@backstage/plugin-events-backend-module-aws-sqs": "0.4.17", - "@backstage/plugin-events-backend-module-azure": "0.2.26", - "@backstage/plugin-events-backend-module-bitbucket-cloud": "0.2.26", - "@backstage/plugin-events-backend-module-bitbucket-server": "0.1.7", - "@backstage/plugin-events-backend-module-gerrit": "0.2.26", - "@backstage/plugin-events-backend-module-github": "0.4.6", - "@backstage/plugin-events-backend-module-gitlab": "0.3.7", - "@backstage/plugin-events-backend-module-google-pubsub": "0.1.6", - "@backstage/plugin-events-backend-module-kafka": "0.1.5", - "@backstage/plugin-events-backend-test-utils": "0.1.50", - "@backstage/plugin-events-node": "0.4.17", - "@internal/plugin-todo-list": "1.0.45", - "@internal/plugin-todo-list-backend": "1.0.45", + "@backstage/plugin-catalog-graph": "0.5.4", + "@backstage/plugin-catalog-import": "0.13.8", + "@backstage/plugin-catalog-node": "1.20.1", + "@backstage/plugin-catalog-react": "1.21.4", + "@backstage/plugin-catalog-unprocessed-entities": "0.2.24", + "@backstage/plugin-catalog-unprocessed-entities-common": "0.0.12", + "@backstage/plugin-config-schema": "0.1.75", + "@backstage/plugin-devtools": "0.1.34", + "@backstage/plugin-devtools-backend": "0.5.12", + "@backstage/plugin-devtools-common": "0.1.20", + "@backstage/plugin-events-backend": "0.5.9", + "@backstage/plugin-events-backend-module-aws-sqs": "0.4.18", + "@backstage/plugin-events-backend-module-azure": "0.2.27", + "@backstage/plugin-events-backend-module-bitbucket-cloud": "0.2.27", + "@backstage/plugin-events-backend-module-bitbucket-server": "0.1.8", + "@backstage/plugin-events-backend-module-gerrit": "0.2.27", + "@backstage/plugin-events-backend-module-github": "0.4.7", + "@backstage/plugin-events-backend-module-gitlab": "0.3.8", + "@backstage/plugin-events-backend-module-google-pubsub": "0.1.7", + "@backstage/plugin-events-backend-module-kafka": "0.2.0", + "@backstage/plugin-events-backend-test-utils": "0.1.51", + "@backstage/plugin-events-node": "0.4.18", + "@internal/plugin-todo-list": "1.0.46", + "@internal/plugin-todo-list-backend": "1.0.46", "@internal/plugin-todo-list-common": "1.0.28", - "@backstage/plugin-gateway-backend": "1.1.0", - "@backstage/plugin-home": "0.8.14", - "@backstage/plugin-home-react": "0.1.32", - "@backstage/plugin-kubernetes": "0.12.13", - "@backstage/plugin-kubernetes-backend": "0.20.4", - "@backstage/plugin-kubernetes-cluster": "0.0.31", - "@backstage/plugin-kubernetes-common": "0.9.8", - "@backstage/plugin-kubernetes-node": "0.3.6", - "@backstage/plugin-kubernetes-react": "0.5.13", - "@backstage/plugin-mcp-actions-backend": "0.1.5", - "@backstage/plugin-mui-to-bui": "0.2.1", - "@backstage/plugin-notifications": "0.5.11", - "@backstage/plugin-notifications-backend": "0.6.0", - "@backstage/plugin-notifications-backend-module-email": "0.3.16", - "@backstage/plugin-notifications-backend-module-slack": "0.2.1", + "@backstage/plugin-gateway-backend": "1.1.1", + "@backstage/plugin-home": "0.8.15", + "@backstage/plugin-home-react": "0.1.33", + "@backstage/plugin-kubernetes": "0.12.14", + "@backstage/plugin-kubernetes-backend": "0.21.0", + "@backstage/plugin-kubernetes-cluster": "0.0.32", + "@backstage/plugin-kubernetes-common": "0.9.9", + "@backstage/plugin-kubernetes-node": "0.4.0", + "@backstage/plugin-kubernetes-react": "0.5.14", + "@backstage/plugin-mcp-actions-backend": "0.1.6", + "@backstage/plugin-mui-to-bui": "0.2.2", + "@backstage/plugin-notifications": "0.5.12", + "@backstage/plugin-notifications-backend": "0.6.1", + "@backstage/plugin-notifications-backend-module-email": "0.3.17", + "@backstage/plugin-notifications-backend-module-slack": "0.3.0", "@backstage/plugin-notifications-common": "0.2.0", - "@backstage/plugin-notifications-node": "0.2.21", - "@backstage/plugin-org": "0.6.46", - "@backstage/plugin-org-react": "0.1.44", - "@backstage/plugin-permission-backend": "0.7.6", - "@backstage/plugin-permission-backend-module-allow-all-policy": "0.2.14", + "@backstage/plugin-notifications-node": "0.2.22", + "@backstage/plugin-org": "0.6.47", + "@backstage/plugin-org-react": "0.1.45", + "@backstage/plugin-permission-backend": "0.7.7", + "@backstage/plugin-permission-backend-module-allow-all-policy": "0.2.15", "@backstage/plugin-permission-common": "0.9.3", - "@backstage/plugin-permission-node": "0.10.6", - "@backstage/plugin-permission-react": "0.4.38", - "@backstage/plugin-proxy-backend": "0.6.8", - "@backstage/plugin-proxy-node": "0.1.10", - "@backstage/plugin-scaffolder": "1.34.3", - "@backstage/plugin-scaffolder-backend": "3.0.1", - "@backstage/plugin-scaffolder-backend-module-azure": "0.2.15", - "@backstage/plugin-scaffolder-backend-module-bitbucket": "0.3.16", - "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud": "0.2.15", - "@backstage/plugin-scaffolder-backend-module-bitbucket-server": "0.2.15", - "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "0.3.15", - "@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.3.17", - "@backstage/plugin-scaffolder-backend-module-gcp": "0.2.15", - "@backstage/plugin-scaffolder-backend-module-gerrit": "0.2.15", - "@backstage/plugin-scaffolder-backend-module-gitea": "0.2.15", - "@backstage/plugin-scaffolder-backend-module-github": "0.9.2", - "@backstage/plugin-scaffolder-backend-module-gitlab": "0.10.0", - "@backstage/plugin-scaffolder-backend-module-notifications": "0.1.16", - "@backstage/plugin-scaffolder-backend-module-rails": "0.5.15", - "@backstage/plugin-scaffolder-backend-module-sentry": "0.2.15", - "@backstage/plugin-scaffolder-backend-module-yeoman": "0.4.16", - "@backstage/plugin-scaffolder-common": "1.7.3", - "@backstage/plugin-scaffolder-node": "0.12.1", - "@backstage/plugin-scaffolder-node-test-utils": "0.3.5", - "@backstage/plugin-scaffolder-react": "1.19.3", - "@backstage/plugin-search": "1.5.0", - "@backstage/plugin-search-backend": "2.0.8", - "@backstage/plugin-search-backend-module-catalog": "0.3.10", - "@backstage/plugin-search-backend-module-elasticsearch": "1.7.8", - "@backstage/plugin-search-backend-module-explore": "0.3.9", - "@backstage/plugin-search-backend-module-pg": "0.5.50", - "@backstage/plugin-search-backend-module-stack-overflow-collator": "0.3.15", - "@backstage/plugin-search-backend-module-techdocs": "0.4.8", - "@backstage/plugin-search-backend-node": "1.3.17", + "@backstage/plugin-permission-node": "0.10.7", + "@backstage/plugin-permission-react": "0.4.39", + "@backstage/plugin-proxy-backend": "0.6.9", + "@backstage/plugin-proxy-node": "0.1.11", + "@backstage/plugin-scaffolder": "1.35.0", + "@backstage/plugin-scaffolder-backend": "3.1.0", + "@backstage/plugin-scaffolder-backend-module-azure": "0.2.16", + "@backstage/plugin-scaffolder-backend-module-bitbucket": "0.3.17", + "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud": "0.3.0", + "@backstage/plugin-scaffolder-backend-module-bitbucket-server": "0.2.16", + "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "0.3.16", + "@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.3.18", + "@backstage/plugin-scaffolder-backend-module-gcp": "0.2.16", + "@backstage/plugin-scaffolder-backend-module-gerrit": "0.2.16", + "@backstage/plugin-scaffolder-backend-module-gitea": "0.2.16", + "@backstage/plugin-scaffolder-backend-module-github": "0.9.3", + "@backstage/plugin-scaffolder-backend-module-gitlab": "0.11.0", + "@backstage/plugin-scaffolder-backend-module-notifications": "0.1.17", + "@backstage/plugin-scaffolder-backend-module-rails": "0.5.16", + "@backstage/plugin-scaffolder-backend-module-sentry": "0.2.16", + "@backstage/plugin-scaffolder-backend-module-yeoman": "0.4.17", + "@backstage/plugin-scaffolder-common": "1.7.4", + "@backstage/plugin-scaffolder-node": "0.12.2", + "@backstage/plugin-scaffolder-node-test-utils": "0.3.6", + "@backstage/plugin-scaffolder-react": "1.19.4", + "@backstage/plugin-search": "1.5.1", + "@backstage/plugin-search-backend": "2.0.9", + "@backstage/plugin-search-backend-module-catalog": "0.3.11", + "@backstage/plugin-search-backend-module-elasticsearch": "1.7.9", + "@backstage/plugin-search-backend-module-explore": "0.3.10", + "@backstage/plugin-search-backend-module-pg": "0.5.51", + "@backstage/plugin-search-backend-module-stack-overflow-collator": "0.3.16", + "@backstage/plugin-search-backend-module-techdocs": "0.4.9", + "@backstage/plugin-search-backend-node": "1.4.0", "@backstage/plugin-search-common": "1.2.21", - "@backstage/plugin-search-react": "1.10.0", - "@backstage/plugin-signals": "0.0.25", - "@backstage/plugin-signals-backend": "0.3.10", - "@backstage/plugin-signals-node": "0.1.26", - "@backstage/plugin-signals-react": "0.0.17", - "@backstage/plugin-techdocs": "1.16.0", - "@backstage/plugin-techdocs-addons-test-utils": "1.1.2", - "@backstage/plugin-techdocs-backend": "2.1.2", + "@backstage/plugin-search-react": "1.10.1", + "@backstage/plugin-signals": "0.0.26", + "@backstage/plugin-signals-backend": "0.3.11", + "@backstage/plugin-signals-node": "0.1.27", + "@backstage/plugin-signals-react": "0.0.18", + "@backstage/plugin-techdocs": "1.16.1", + "@backstage/plugin-techdocs-addons-test-utils": "2.0.0", + "@backstage/plugin-techdocs-backend": "2.1.3", "@backstage/plugin-techdocs-common": "0.1.1", - "@backstage/plugin-techdocs-module-addons-contrib": "1.1.30", - "@backstage/plugin-techdocs-node": "1.13.9", - "@backstage/plugin-techdocs-react": "1.3.5", - "@backstage/plugin-user-settings": "0.8.29", - "@backstage/plugin-user-settings-backend": "0.3.8", + "@backstage/plugin-techdocs-module-addons-contrib": "1.1.31", + "@backstage/plugin-techdocs-node": "1.13.10", + "@backstage/plugin-techdocs-react": "1.3.6", + "@backstage/plugin-user-settings": "0.8.30", + "@backstage/plugin-user-settings-backend": "0.3.9", "@backstage/plugin-user-settings-common": "0.0.1" }, "changesets": [] diff --git a/.changeset/proud-streets-switch.md b/.changeset/proud-streets-switch.md new file mode 100644 index 0000000000..188b66f178 --- /dev/null +++ b/.changeset/proud-streets-switch.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-oidc-provider': patch +--- + +Added support for custom start URL search parameters (with the new `startUrlSearchParams` config property) diff --git a/.changeset/shaggy-bats-film.md b/.changeset/shaggy-bats-film.md new file mode 100644 index 0000000000..3e5be9648b --- /dev/null +++ b/.changeset/shaggy-bats-film.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': minor +--- + +**BREAKING**: Renamed CSS variable `--bui-bg` to `--bui-bg-surface-0` for consistency. diff --git a/.changeset/spicy-teeth-study.md b/.changeset/spicy-teeth-study.md deleted file mode 100644 index 03cc8a5440..0000000000 --- a/.changeset/spicy-teeth-study.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/ui': patch ---- - -Fixed `useTable` hook to prioritize `providedRowCount` over data length for accurate row count in server-side pagination scenarios. diff --git a/.changeset/thin-teams-roll.md b/.changeset/thin-teams-roll.md new file mode 100644 index 0000000000..f0c395410a --- /dev/null +++ b/.changeset/thin-teams-roll.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +Fixed a spelling mistake in root health service shutdown response. diff --git a/.changeset/weak-lemons-create.md b/.changeset/weak-lemons-create.md new file mode 100644 index 0000000000..344a6660ce --- /dev/null +++ b/.changeset/weak-lemons-create.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Bump minimum required `@swc/core` to avoid transpilation bug diff --git a/.changeset/wild-toys-retire.md b/.changeset/wild-toys-retire.md new file mode 100644 index 0000000000..f7179e2527 --- /dev/null +++ b/.changeset/wild-toys-retire.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-mui-to-bui': patch +--- + +Updated tokens from `--bui-bg` to `--bui-bg-surface-0` diff --git a/.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 7c780c1079..44ee42a25a 100644 --- a/.github/vale/config/vocabularies/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -268,6 +268,7 @@ maintainer's maintainership makefile Matomo +matchers md memcache memoization @@ -509,6 +510,7 @@ thumbsup todo todos togglable +tokenizer tolerations Tolerations toolchain diff --git a/.github/workflows/api-breaking-changes-comment.yml b/.github/workflows/api-breaking-changes-comment.yml index 9b20cf80fb..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + 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@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ @@ -99,7 +99,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - name: Fetch cached Manifests File id: cache diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index 458aa621dc..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - 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 b1663d1629..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + 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 99ffe2f62d..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - 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 3dfb3e222e..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - 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@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + 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 cbdab4414a..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + 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 d046686002..12aafe18ee 100644 --- a/.github/workflows/ci-noop.yml +++ b/.github/workflows/ci-noop.yml @@ -19,7 +19,7 @@ jobs: strategy: matrix: - node-version: [20.x, 22.x] + node-version: [22.x, 24.x] name: Verify ${{ matrix.node-version }} steps: @@ -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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + 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 db71826ef4..36b4114efe 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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - 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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - 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: @@ -203,14 +203,14 @@ jobs: env: CI: true - NODE_OPTIONS: --max-old-space-size=8192 --no-node-snapshot --experimental-vm-modules + NODE_OPTIONS: --max-old-space-size=8192 --experimental-vm-modules INTEGRATION_TEST_GITHUB_TOKEN: ${{ secrets.INTEGRATION_TEST_GITHUB_TOKEN }} INTEGRATION_TEST_GITLAB_TOKEN: ${{ secrets.INTEGRATION_TEST_GITLAB_TOKEN }} INTEGRATION_TEST_BITBUCKET_TOKEN: ${{ secrets.INTEGRATION_TEST_BITBUCKET_TOKEN }} INTEGRATION_TEST_AZURE_TOKEN: ${{ secrets.INTEGRATION_TEST_AZURE_TOKEN }} steps: - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: fetch master branch run: git fetch origin master 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 bb759971b7..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + 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 ee4866d7af..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - name: checkout - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + 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 acf0cb7776..08f349c7ec 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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - name: find latest release - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # 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@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + 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 @@ -135,23 +135,23 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - name: checkout master - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + 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 @@ -240,19 +240,19 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + 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@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + 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@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + 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@4a3abc783e1a24aeb44c16e869ad83caf6b4cc23 # v4.7.4 + uses: JamesIves/github-pages-deploy-action@9d877eea73427180ae43cf98e8914934fe157a1a # v4.7.6 with: branch: gh-pages folder: microsite/build diff --git a/.github/workflows/deploy_packages.yml b/.github/workflows/deploy_packages.yml index 4b35921b42..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@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 @@ -147,11 +147,11 @@ jobs: strategy: matrix: - node-version: [20.x] + node-version: [22.x] steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + 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 2eefcfcd88..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + 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@08eba0b27e820071cde6df949e0beb9ba4906955 # 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 index 8c91e0e8a0..7ed8013a3b 100644 --- a/.github/workflows/mui-migration-tracker.yml +++ b/.github/workflows/mui-migration-tracker.yml @@ -18,23 +18,23 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - name: Setup Node.js 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: Run migration analysis id: analysis @@ -48,7 +48,7 @@ jobs: echo "EOF" >> $GITHUB_ENV - name: Update GitHub Issue - uses: actions/github-script@v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | @@ -62,7 +62,7 @@ jobs: issue_number: issueNumber, body: reportBody }); - + console.log(`✅ Successfully updated issue #${issueNumber}`); } catch (error) { console.error(`❌ Error updating issue: ${error.message}`); diff --git a/.github/workflows/pr-review-comment-trigger.yaml b/.github/workflows/pr-review-comment-trigger.yaml index 968bc4ab12..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + 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 122eb4fcce..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - name: Read PR Number id: pr-number - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + 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 4067326890..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + 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 d42fb0af6c..077eb2d856 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -29,12 +29,12 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - name: 'Checkout code' - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false @@ -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@f47c8e6a9bd05ef3ee422fc8d8663be7fe4bdc61 # v3.31.8 with: sarif_file: results.sarif diff --git a/.github/workflows/sync_canon.yml b/.github/workflows/sync_canon.yml index db8bfb6b19..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - name: Checkout - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + 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@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + 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 478c8a0cf2..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - 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 2ddde5ca4b..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - name: Checkout - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + 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@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + 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..bdf93aa8a0 --- /dev/null +++ b/.github/workflows/sync_patch-release.yml @@ -0,0 +1,258 @@ +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: Use Node.js 22.x + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: 22.x + registry-url: https://registry.npmjs.org/ # Needed for auth + + - name: Configure Git + run: | + git config --global user.email noreply@backstage.io + 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 0e9aa213c0..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - name: Checkout - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + 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@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + 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@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + 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 5f98f80615..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - name: Checkout - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + 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@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + 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 d56fac654a..97f7e8ed14 100644 --- a/.github/workflows/sync_snyk-github-issues.yml +++ b/.github/workflows/sync_snyk-github-issues.yml @@ -12,21 +12,21 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - 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@9adf32b1121593767fc3c057af55b55db032dc04 # master diff --git a/.github/workflows/sync_snyk-monitor.yml b/.github/workflows/sync_snyk-monitor.yml index 0d7befd3b7..632d03c35d 100644 --- a/.github/workflows/sync_snyk-monitor.yml +++ b/.github/workflows/sync_snyk-monitor.yml @@ -25,11 +25,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Monitor and Synchronize Snyk Policies uses: snyk/actions/node@9adf32b1121593767fc3c057af55b55db032dc04 # master with: @@ -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@f47c8e6a9bd05ef3ee422fc8d8663be7fe4bdc61 # v3.31.8 with: sarif_file: snyk.sarif diff --git a/.github/workflows/sync_version-packages.yml b/.github/workflows/sync_version-packages.yml index 7ca74c6aeb..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - 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 abb3abc665..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + 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 3b624c96bc..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - - 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 2a2ad7d43d..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + 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 e9f34e35ac..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - name: Checkout code - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 10000 # Required to retrieve git history diff --git a/.github/workflows/verify_codeql.yml b/.github/workflows/verify_codeql.yml index 7dad08fa01..5d976f8583 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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - name: Checkout repository - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + 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@f47c8e6a9bd05ef3ee422fc8d8663be7fe4bdc61 # v3.31.8 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -66,7 +66,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@a4e1a019f5e24960714ff6296aee04b736cbc3cf # v3.29.6 + uses: github/codeql-action/autobuild@f47c8e6a9bd05ef3ee422fc8d8663be7fe4bdc61 # v3.31.8 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -80,4 +80,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@a4e1a019f5e24960714ff6296aee04b736cbc3cf # v3.29.6 + uses: github/codeql-action/analyze@f47c8e6a9bd05ef3ee422fc8d8663be7fe4bdc61 # v3.31.8 diff --git a/.github/workflows/verify_docs-quality.yml b/.github/workflows/verify_docs-quality.yml index 5f31c7a289..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - 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 cc25b2db58..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + 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 25d71eb641..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - 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 6bb8f11651..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - 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 f8681e8b10..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + 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 f98fc98745..f63eb5d61b 100644 --- a/.github/workflows/verify_e2e-windows.yml +++ b/.github/workflows/verify_e2e-windows.yml @@ -24,16 +24,16 @@ jobs: strategy: matrix: - node-version: [20.x, 22.x] + node-version: [22.x, 24.x] env: CI: true - NODE_OPTIONS: --max-old-space-size=8192 --no-node-snapshot --experimental-vm-modules + NODE_OPTIONS: --max-old-space-size=8192 --experimental-vm-modules name: E2E Windows ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + 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@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - 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 732e53988a..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - name: Checkout - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + 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 a6edeb8415..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + 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 689b85fe9f..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - name: find latest release - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # 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@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + 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 @@ -137,23 +137,23 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - name: checkout master - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + 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 @@ -234,16 +234,16 @@ jobs: name: Microsite steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - 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@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + 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 f4263e1398..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + 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 86196f7953..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@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - 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 ba24410145..f7de04772d 100644 --- a/.github/workflows/verify_windows.yml +++ b/.github/workflows/verify_windows.yml @@ -17,11 +17,11 @@ jobs: strategy: fail-fast: false matrix: - node-version: [20.x, 22.x] + node-version: [22.x, 24.x] env: CI: true - NODE_OPTIONS: --max-old-space-size=8192 --no-node-snapshot --experimental-vm-modules + NODE_OPTIONS: --max-old-space-size=8192 --experimental-vm-modules INTEGRATION_TEST_GITHUB_TOKEN: ${{ secrets.INTEGRATION_TEST_GITHUB_TOKEN }} INTEGRATION_TEST_GITLAB_TOKEN: ${{ secrets.INTEGRATION_TEST_GITLAB_TOKEN }} INTEGRATION_TEST_BITBUCKET_TOKEN: ${{ secrets.INTEGRATION_TEST_BITBUCKET_TOKEN }} @@ -29,11 +29,11 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 with: egress-policy: audit - - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - 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 index 5d47ba77da..9061b70d79 100644 --- a/.github/workflows/welcome.yml +++ b/.github/workflows/welcome.yml @@ -15,7 +15,7 @@ jobs: if: github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' steps: - name: Add a welcome comment - uses: actions/github-script@v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | 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/.storybook/main.ts b/.storybook/main.ts index 788df83ba9..5453627f34 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,7 +1,13 @@ -import type { StorybookConfig } from '@storybook/react-vite'; +import { defineMain } from '@storybook/react-vite/node'; +import { fileURLToPath } from 'node:url'; +import { createRequire } from 'node:module'; import { join, dirname, posix } from 'path'; +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const require = createRequire(import.meta.url); + const isChromatic = process.env.STORYBOOK_STORY_SET === 'chromatic'; // All stories for full development @@ -35,13 +41,14 @@ function getAbsolutePath(value: string): any { return dirname(require.resolve(join(value, 'package.json'))); } -const config: StorybookConfig = { +export default defineMain({ stories, addons: [ getAbsolutePath('@storybook/addon-links'), getAbsolutePath('@storybook/addon-themes'), getAbsolutePath('@storybook/addon-docs'), getAbsolutePath('@storybook/addon-a11y'), + getAbsolutePath('@storybook/addon-vitest'), ], framework: { name: getAbsolutePath('@storybook/react-vite'), @@ -122,6 +129,4 @@ const config: StorybookConfig = { return config; }, -}; - -export default config; +}); diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 8f987ebab1..4e15677aed 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -1,8 +1,12 @@ -import React, { useEffect } from 'react'; +import addonA11y from '@storybook/addon-a11y'; +import addonDocs from '@storybook/addon-docs'; +import addonThemes from '@storybook/addon-themes'; +import addonLinks from '@storybook/addon-links'; +import { definePreview } from '@storybook/react-vite'; +import { useEffect } from 'react'; import { TestApiProvider } from '@backstage/test-utils'; import { Content, AlertDisplay } from '@backstage/core-components'; import { apis } from './support/apis'; -import type { Decorator, Preview } from '@storybook/react-vite'; import { useGlobals } from 'storybook/preview-api'; import { UnifiedThemeProvider, themes } from '@backstage/theme'; import { allModes } from './modes'; @@ -16,7 +20,7 @@ import './storybook.css'; // Custom themes import './themes/spotify.css'; -const preview: Preview = { +export default definePreview({ globalTypes: { themeMode: { name: 'Theme Mode', @@ -47,10 +51,12 @@ const preview: Preview = { }, }, }, + initialGlobals: { themeMode: 'light', themeName: 'backstage', }, + parameters: { layout: 'fullscreen', @@ -72,7 +78,7 @@ const preview: Preview = { }, viewport: { - viewports: { + options: { initial: { name: 'Initial', styles: { width: '320px', height: '100%' }, @@ -102,6 +108,7 @@ const preview: Preview = { }, }, }, + decorators: [ Story => { const [globals] = useGlobals(); @@ -121,10 +128,11 @@ const preview: Preview = { }; }, [selectedTheme, selectedThemeName]); - document.body.style.backgroundColor = 'var(--bui-bg)'; + document.body.style.backgroundColor = 'var(--bui-bg-surface-0)'; const docsStoryElements = document.getElementsByClassName('docs-story'); Array.from(docsStoryElements).forEach(element => { - (element as HTMLElement).style.backgroundColor = 'var(--bui-bg)'; + (element as HTMLElement).style.backgroundColor = + 'var(--bui-bg-surface-0)'; }); return ( @@ -140,6 +148,6 @@ const preview: Preview = { ); }, ], -}; -export default preview; + addons: [addonLinks(), addonThemes(), addonDocs(), addonA11y()], +}); diff --git a/.storybook/themes/spotify.css b/.storybook/themes/spotify.css index bcb1ded9ff..db3a0ca16c 100644 --- a/.storybook/themes/spotify.css +++ b/.storybook/themes/spotify.css @@ -216,9 +216,6 @@ } [data-theme-mode='light'][data-theme-name='spotify'] { - --bui-bg: var(--bui-gray-1); - --bui-bg-surface-1: var(--bui-white); - --bui-bg-surface-2: var(--bui-gray-2); --bui-bg-solid: #1ed760; --bui-bg-solid-hover: #3be477; --bui-bg-solid-pressed: #1abc54; @@ -248,9 +245,7 @@ } [data-theme-mode='dark'][data-theme-name='spotify'] { - --bui-bg: var(--bui-black); - --bui-bg-surface-1: var(--bui-gray-1); - --bui-bg-surface-2: var(--bui-gray-2); + --bui-bg-surface-0: var(--bui-black); --bui-bg-solid: #1ed760; --bui-bg-solid-hover: #3be477; --bui-bg-solid-pressed: #1abc54; diff --git a/ADOPTERS.md b/ADOPTERS.md index 961d9d534e..2b3cad0b25 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -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..764237d9c1 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -5,11 +5,13 @@ Team: @backstage/maintainers -| Maintainer | Organization | GitHub | Discord | -| --------------- | ------------ | ----------------------------------------------- | ------------- | -| Patrik Oldsberg | Spotify | [Rugvip](https://github.com/Rugvip) | `Rugvip#0019` | -| Fredrik Adelöw | Spotify | [freben](https://github.com/freben) | `freben#3926` | -| Ben Lambert | Spotify | [benjdlambert](https://github.com/benjdlambert) | `blam#2159` | +| Maintainer | Organization | GitHub | Discord | +| --------------- | ------------ | ----------------------------------------------------- | ------------- | +| André Wanlin | Spotify | [awanlin](https://github.com/awanlin) | `ahhhndre` | +| Aramis Sennyey | DoorDash | [aramissennyeydd](https://github.com/aramissennyeydd) | `Aramis#7984` | +| Ben Lambert | Spotify | [benjdlambert](https://github.com/benjdlambert) | `blam#2159` | +| Fredrik Adelöw | Spotify | [freben](https://github.com/freben) | `freben#3926` | +| Patrik Oldsberg | Spotify | [Rugvip](https://github.com/Rugvip) | `Rugvip#0019` | ## Project Areas @@ -45,6 +47,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 @@ -64,7 +80,7 @@ Scope: The Backstage Documentation and Microsite, excluding the plugins listing | Name | Organization | GitHub | Discord | | --------------- | ------------- | ----------------------------------------------------- | ------------- | -| Andre Wanlin | Spotify | [awanlin](https://github.com/awanlin) | `ahhhndre` | +| André Wanlin | Spotify | [awanlin](https://github.com/awanlin) | `ahhhndre` | | Aramis Sennyey | DoorDash | [aramissennyeydd](https://github.com/aramissennyeydd) | `Aramis#7984` | | Peter Macdonald | VodafoneZiggo | [Parsifal-M](https://github.com/Parsifal-M) | `parsifal` | @@ -206,20 +222,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 @@ -267,6 +269,7 @@ Scope: The Scaffolder frontend and backend plugins, and related tooling. | Niklas Gustavsson | Spotify | [protocol7](https://github.com/protocol7) | | | Dave Zolotusky | Spotify | [dzolotusky](https://github.com/dzolotusky) | | | Pia Nilsson | Spotify | [pianilsson](https://github.com/pianilsson) | | +| Stefan Särne | Spotify | [ssarne](https://github.com/ssarne) | | ## Organization Members @@ -274,9 +277,7 @@ Scope: The Scaffolder frontend and backend plugins, and related tooling. | ------------------------------ | ------------------------- | ----------------------------------------------------- | ------------------------------ | | Adam Harvey | Okta | [adamdmharvey](https://github.com/adamdmharvey) | `adamharvey_` | | Alex Crome | | [afscrome](https://github.com/afscrome) | `afscrome` | -| Andre Wanlin | Spotify | [awanlin](https://github.com/awanlin) | `ahhhndre` | | Andrew Thauer | Wealthsimple | [andrewthauer](https://github.com/andrewthauer) | `andrewthauer#3060` | -| Aramis Sennyey | DoorDash | [aramissennyeydd](https://github.com/aramissennyeydd) | `Aramis#7984` | | Brian Fletcher | Roadie.io | [punkle](https://github.com/punkle) | `Brian Fletcher#7051` | | Carlos Esteban Lopez Jaramillo | VMWare | [luchillo17](https://github.com/luchillo17) | `luchillo17#8777` | | David Tuite | Roadie.io | [dtuite](https://github.com/dtuite) | `David Tuite (roadie.io)#1010` | diff --git a/README.md b/README.md index 545874adf9..e3e9bd77dd 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,3 @@ -> [!NOTE] -> ✈️ From Monday November 10th through November 14rd, several maintainers and Spotify employees will be at KubeCon / BackstageCon! Expect the project to move a little slower than normal, and support to be limited. Normal service will resume after that! And do come visit our booth if you are there. ✈️ - [![headline](docs/assets/headline.png)](https://backstage.io/) # [Backstage](https://backstage.io) diff --git a/app-config.yaml b/app-config.yaml index 1489a39105..54ece44da3 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -109,8 +109,12 @@ integrations: token: ${GITLAB_TOKEN} ### Example for how to add a bitbucket cloud integration # bitbucketCloud: + # # Using API token # - username: ${BITBUCKET_USERNAME} # token: ${BITBUCKET_API_TOKEN} + # # Using OAuth + # - clientId: ${BITBUCKET_CLIENT_ID} + # clientSecret: ${BITBUCKET_CLIENT_SECRET} ### Example for how to add your bitbucket server instance using the API: # - host: server.bitbucket.com # apiBaseUrl: server.bitbucket.com @@ -209,6 +213,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: @@ -306,3 +315,8 @@ auth: permission: enabled: true + +devTools: + scheduledTasks: + plugins: + - catalog 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/docker-compose.deps.yml b/docker-compose.deps.yml index 2ad65d6085..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 diff --git a/docs-ui/package.json b/docs-ui/package.json index 319c0e130f..2f17987107 100644 --- a/docs-ui/package.json +++ b/docs-ui/package.json @@ -24,7 +24,7 @@ "@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", @@ -32,24 +32,24 @@ "clsx": "^2.1.1", "html-react-parser": "^5.2.5", "motion": "^12.4.1", - "next": "15.4.7", + "next": "15.5.9", "next-mdx-remote-client": "^2.1.2", "prop-types": "^15.8.1", "react": "19.1.1", "react-dom": "19.1.1", "shiki": "^3.13.0", - "storybook": "^8.6.12" + "storybook": "^8.6.15" }, "devDependencies": { "@octokit/rest": "^22.0.1", "@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", "eslint": "^8", - "eslint-config-next": "15.3.4", + "eslint-config-next": "15.5.7", "lightningcss": "^1.28.2", "typescript": "^5", "unified": "^11.0.4" diff --git a/docs-ui/public/theme-backstage.css b/docs-ui/public/theme-backstage.css index 59a5b986be..9b59448c11 100644 --- a/docs-ui/public/theme-backstage.css +++ b/docs-ui/public/theme-backstage.css @@ -1 +1 @@ -@layer tokens{:root{--bui-font-regular:system-ui;--bui-font-monospace:ui-monospace,"Menlo","Monaco","Consolas","Liberation Mono","Courier New",monospace;--bui-font-weight-regular:400;--bui-font-weight-bold:600;--bui-font-size-1:.625rem;--bui-font-size-2:.75rem;--bui-font-size-3:.875rem;--bui-font-size-4:1rem;--bui-font-size-5:1.25rem;--bui-font-size-6:1.5rem;--bui-font-size-7:2rem;--bui-font-size-8:3rem;--bui-font-size-9:4rem;--bui-font-size-10:5.75rem;--bui-space:.25rem;--bui-space-0_5:calc(var(--bui-space)*.5);--bui-space-1:var(--bui-space);--bui-space-1_5:calc(var(--bui-space)*1.5);--bui-space-2:calc(var(--bui-space)*2);--bui-space-3:calc(var(--bui-space)*3);--bui-space-4:calc(var(--bui-space)*4);--bui-space-5:calc(var(--bui-space)*5);--bui-space-6:calc(var(--bui-space)*6);--bui-space-7:calc(var(--bui-space)*7);--bui-space-8:calc(var(--bui-space)*8);--bui-space-9:calc(var(--bui-space)*9);--bui-space-10:calc(var(--bui-space)*10);--bui-space-11:calc(var(--bui-space)*11);--bui-space-12:calc(var(--bui-space)*12);--bui-space-13:calc(var(--bui-space)*13);--bui-space-14:calc(var(--bui-space)*14);--bui-radius-1:calc(.125rem);--bui-radius-2:calc(.25rem);--bui-radius-3:calc(.5rem);--bui-radius-4:calc(.75rem);--bui-radius-5:calc(1rem);--bui-radius-6:calc(1.25rem);--bui-radius-full:9999px;--bui-black:#000;--bui-white:#fff;--bui-gray-1:#f8f8f8;--bui-gray-2:#ececec;--bui-gray-3:#d9d9d9;--bui-gray-4:#c1c1c1;--bui-gray-5:#9e9e9e;--bui-gray-6:#8c8c8c;--bui-gray-7:#757575;--bui-gray-8:#595959;--bui-bg:var(--bui-gray-1);--bui-bg-surface-1:var(--bui-white);--bui-bg-surface-2:var(--bui-gray-2);--bui-bg-solid:#1f5493;--bui-bg-solid-hover:#163a66;--bui-bg-solid-pressed:#0f2b4e;--bui-bg-solid-disabled:#ebebeb;--bui-bg-tint:transparent;--bui-bg-tint-hover:#1f549366;--bui-bg-tint-pressed:#1f549399;--bui-bg-tint-disabled:#ebebeb;--bui-bg-danger:#feebe7;--bui-bg-warning:#fff2b2;--bui-bg-success:#e6f6eb;--bui-fg-primary:var(--bui-black);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:#1f5493;--bui-fg-link-hover:#1f2d5c;--bui-fg-disabled:#9e9e9e;--bui-fg-solid:var(--bui-white);--bui-fg-solid-disabled:#9c9c9c;--bui-fg-tint:#1f5493;--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#e22b2b;--bui-fg-warning:#e36d05;--bui-fg-success:#1db954;--bui-border:#0000001a;--bui-border-hover:#0003;--bui-border-pressed:#0006;--bui-border-disabled:#0000001a;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#1f5493;--bui-scrollbar:#a0a0a03b;--bui-scrollbar-thumb:#a0a0a0;--bui-animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite}[data-theme-mode=dark]{--bui-gray-1:#191919;--bui-gray-2:#242424;--bui-gray-3:#373737;--bui-gray-4:#464646;--bui-gray-5:#575757;--bui-gray-6:#7b7b7b;--bui-gray-7:#9e9e9e;--bui-gray-8:#b4b4b4;--bui-bg:#333;--bui-bg-surface-1:#424242;--bui-bg-surface-2:var(--bui-gray-2);--bui-bg-solid:#9cc9ff;--bui-bg-solid-hover:#83b9fd;--bui-bg-solid-pressed:#83b9fd;--bui-bg-solid-disabled:#222;--bui-bg-tint:transparent;--bui-bg-tint-hover:#9cc9ff1f;--bui-bg-tint-pressed:#9cc9ff29;--bui-bg-tint-disabled:transparent;--bui-bg-danger:#3b1219;--bui-bg-warning:#302008;--bui-bg-success:#132d21;--bui-fg-primary:var(--bui-white);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:#9cc9ff;--bui-fg-link-hover:#7eb5f7;--bui-fg-disabled:var(--bui-gray-7);--bui-fg-solid:#101821;--bui-fg-solid-disabled:var(--bui-gray-5);--bui-fg-tint:#9cc9ff;--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#e22b2b;--bui-fg-warning:#e36d05;--bui-fg-success:#1db954;--bui-border:#ffffff1f;--bui-border-hover:#fff6;--bui-border-pressed:#ffffff80;--bui-border-disabled:#fff3;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#1f5493;--bui-scrollbar:#3636363a;--bui-scrollbar-thumb:#575757}}@layer base{*,:before,:after{box-sizing:border-box}html{-webkit-text-size-adjust:100%;tab-size:4;font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;line-height:1.15}body{margin:0}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{border-color:currentColor}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:100%;line-height:1.15}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}legend{padding:0}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}:where(a){color:inherit;text-decoration:none}@keyframes pulse{50%{opacity:.5}}body{background-color:var(--bui-bg);color:var(--bui-fg-primary);font-family:var(--bui-font-regular);font-weight:var(--bui-font-weight-regular);font-size:var(--bui-font-size-3);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}}@layer components;@layer utilities{.bui-p{padding:var(--p)}.bui-p-0\.5{padding:var(--bui-space-0_5)}.bui-p-1{padding:var(--bui-space-1)}.bui-p-1\.5{padding:var(--bui-space-1_5)}.bui-p-2{padding:var(--bui-space-2)}.bui-p-3{padding:var(--bui-space-3)}.bui-p-4{padding:var(--bui-space-4)}.bui-p-5{padding:var(--bui-space-5)}.bui-p-6{padding:var(--bui-space-6)}.bui-p-7{padding:var(--bui-space-7)}.bui-p-8{padding:var(--bui-space-8)}.bui-p-9{padding:var(--bui-space-9)}.bui-p-10{padding:var(--bui-space-10)}.bui-p-11{padding:var(--bui-space-11)}.bui-p-12{padding:var(--bui-space-12)}.bui-p-13{padding:var(--bui-space-13)}.bui-p-14{padding:var(--bui-space-14)}@media (width>=640px){.xs\:bui-p{padding:var(--p-xs)}.xs\:bui-p-0\.5{padding:var(--bui-space-0_5)}.xs\:bui-p-1{padding:var(--bui-space-1)}.xs\:bui-p-1\.5{padding:var(--bui-space-1_5)}.xs\:bui-p-2{padding:var(--bui-space-2)}.xs\:bui-p-3{padding:var(--bui-space-3)}.xs\:bui-p-4{padding:var(--bui-space-4)}.xs\:bui-p-5{padding:var(--bui-space-5)}.xs\:bui-p-6{padding:var(--bui-space-6)}.xs\:bui-p-7{padding:var(--bui-space-7)}.xs\:bui-p-8{padding:var(--bui-space-8)}.xs\:bui-p-9{padding:var(--bui-space-9)}.xs\:bui-p-10{padding:var(--bui-space-10)}.xs\:bui-p-11{padding:var(--bui-space-11)}.xs\:bui-p-12{padding:var(--bui-space-12)}.xs\:bui-p-13{padding:var(--bui-space-13)}.xs\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-p{padding:var(--p-sm)}.sm\:bui-p-0\.5{padding:var(--bui-space-0_5)}.sm\:bui-p-1{padding:var(--bui-space-1)}.sm\:bui-p-1\.5{padding:var(--bui-space-1_5)}.sm\:bui-p-2{padding:var(--bui-space-2)}.sm\:bui-p-3{padding:var(--bui-space-3)}.sm\:bui-p-4{padding:var(--bui-space-4)}.sm\:bui-p-5{padding:var(--bui-space-5)}.sm\:bui-p-6{padding:var(--bui-space-6)}.sm\:bui-p-7{padding:var(--bui-space-7)}.sm\:bui-p-8{padding:var(--bui-space-8)}.sm\:bui-p-9{padding:var(--bui-space-9)}.sm\:bui-p-10{padding:var(--bui-space-10)}.sm\:bui-p-11{padding:var(--bui-space-11)}.sm\:bui-p-12{padding:var(--bui-space-12)}.sm\:bui-p-13{padding:var(--bui-space-13)}.sm\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-p{padding:var(--p-md)}.md\:bui-p-0\.5{padding:var(--bui-space-0_5)}.md\:bui-p-1{padding:var(--bui-space-1)}.md\:bui-p-1\.5{padding:var(--bui-space-1_5)}.md\:bui-p-2{padding:var(--bui-space-2)}.md\:bui-p-3{padding:var(--bui-space-3)}.md\:bui-p-4{padding:var(--bui-space-4)}.md\:bui-p-5{padding:var(--bui-space-5)}.md\:bui-p-6{padding:var(--bui-space-6)}.md\:bui-p-7{padding:var(--bui-space-7)}.md\:bui-p-8{padding:var(--bui-space-8)}.md\:bui-p-9{padding:var(--bui-space-9)}.md\:bui-p-10{padding:var(--bui-space-10)}.md\:bui-p-11{padding:var(--bui-space-11)}.md\:bui-p-12{padding:var(--bui-space-12)}.md\:bui-p-13{padding:var(--bui-space-13)}.md\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-p{padding:var(--p-lg)}.lg\:bui-p-0\.5{padding:var(--bui-space-0_5)}.lg\:bui-p-1{padding:var(--bui-space-1)}.lg\:bui-p-1\.5{padding:var(--bui-space-1_5)}.lg\:bui-p-2{padding:var(--bui-space-2)}.lg\:bui-p-3{padding:var(--bui-space-3)}.lg\:bui-p-4{padding:var(--bui-space-4)}.lg\:bui-p-5{padding:var(--bui-space-5)}.lg\:bui-p-6{padding:var(--bui-space-6)}.lg\:bui-p-7{padding:var(--bui-space-7)}.lg\:bui-p-8{padding:var(--bui-space-8)}.lg\:bui-p-9{padding:var(--bui-space-9)}.lg\:bui-p-10{padding:var(--bui-space-10)}.lg\:bui-p-11{padding:var(--bui-space-11)}.lg\:bui-p-12{padding:var(--bui-space-12)}.lg\:bui-p-13{padding:var(--bui-space-13)}.lg\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-p{padding:var(--p-xl)}.xl\:bui-p-0\.5{padding:var(--bui-space-0_5)}.xl\:bui-p-1{padding:var(--bui-space-1)}.xl\:bui-p-1\.5{padding:var(--bui-space-1_5)}.xl\:bui-p-2{padding:var(--bui-space-2)}.xl\:bui-p-3{padding:var(--bui-space-3)}.xl\:bui-p-4{padding:var(--bui-space-4)}.xl\:bui-p-5{padding:var(--bui-space-5)}.xl\:bui-p-6{padding:var(--bui-space-6)}.xl\:bui-p-7{padding:var(--bui-space-7)}.xl\:bui-p-8{padding:var(--bui-space-8)}.xl\:bui-p-9{padding:var(--bui-space-9)}.xl\:bui-p-10{padding:var(--bui-space-10)}.xl\:bui-p-11{padding:var(--bui-space-11)}.xl\:bui-p-12{padding:var(--bui-space-12)}.xl\:bui-p-13{padding:var(--bui-space-13)}.xl\:bui-p-14{padding:var(--bui-space-14)}}.bui-pl{padding-left:var(--pl)}.bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.bui-pl-1{padding-left:var(--bui-space-1)}.bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.bui-pl-2{padding-left:var(--bui-space-2)}.bui-pl-3{padding-left:var(--bui-space-3)}.bui-pl-4{padding-left:var(--bui-space-4)}.bui-pl-5{padding-left:var(--bui-space-5)}.bui-pl-6{padding-left:var(--bui-space-6)}.bui-pl-7{padding-left:var(--bui-space-7)}.bui-pl-8{padding-left:var(--bui-space-8)}.bui-pl-9{padding-left:var(--bui-space-9)}.bui-pl-10{padding-left:var(--bui-space-10)}.bui-pl-11{padding-left:var(--bui-space-11)}.bui-pl-12{padding-left:var(--bui-space-12)}.bui-pl-13{padding-left:var(--bui-space-13)}.bui-pl-14{padding-left:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pl{padding-left:var(--pl-xs)}.xs\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.xs\:bui-pl-1{padding-left:var(--bui-space-1)}.xs\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.xs\:bui-pl-2{padding-left:var(--bui-space-2)}.xs\:bui-pl-3{padding-left:var(--bui-space-3)}.xs\:bui-pl-4{padding-left:var(--bui-space-4)}.xs\:bui-pl-5{padding-left:var(--bui-space-5)}.xs\:bui-pl-6{padding-left:var(--bui-space-6)}.xs\:bui-pl-7{padding-left:var(--bui-space-7)}.xs\:bui-pl-8{padding-left:var(--bui-space-8)}.xs\:bui-pl-9{padding-left:var(--bui-space-9)}.xs\:bui-pl-10{padding-left:var(--bui-space-10)}.xs\:bui-pl-11{padding-left:var(--bui-space-11)}.xs\:bui-pl-12{padding-left:var(--bui-space-12)}.xs\:bui-pl-13{padding-left:var(--bui-space-13)}.xs\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pl{padding-left:var(--pl-sm)}.sm\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.sm\:bui-pl-1{padding-left:var(--bui-space-1)}.sm\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.sm\:bui-pl-2{padding-left:var(--bui-space-2)}.sm\:bui-pl-3{padding-left:var(--bui-space-3)}.sm\:bui-pl-4{padding-left:var(--bui-space-4)}.sm\:bui-pl-5{padding-left:var(--bui-space-5)}.sm\:bui-pl-6{padding-left:var(--bui-space-6)}.sm\:bui-pl-7{padding-left:var(--bui-space-7)}.sm\:bui-pl-8{padding-left:var(--bui-space-8)}.sm\:bui-pl-9{padding-left:var(--bui-space-9)}.sm\:bui-pl-10{padding-left:var(--bui-space-10)}.sm\:bui-pl-11{padding-left:var(--bui-space-11)}.sm\:bui-pl-12{padding-left:var(--bui-space-12)}.sm\:bui-pl-13{padding-left:var(--bui-space-13)}.sm\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pl{padding-left:var(--pl-md)}.md\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.md\:bui-pl-1{padding-left:var(--bui-space-1)}.md\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.md\:bui-pl-2{padding-left:var(--bui-space-2)}.md\:bui-pl-3{padding-left:var(--bui-space-3)}.md\:bui-pl-4{padding-left:var(--bui-space-4)}.md\:bui-pl-5{padding-left:var(--bui-space-5)}.md\:bui-pl-6{padding-left:var(--bui-space-6)}.md\:bui-pl-7{padding-left:var(--bui-space-7)}.md\:bui-pl-8{padding-left:var(--bui-space-8)}.md\:bui-pl-9{padding-left:var(--bui-space-9)}.md\:bui-pl-10{padding-left:var(--bui-space-10)}.md\:bui-pl-11{padding-left:var(--bui-space-11)}.md\:bui-pl-12{padding-left:var(--bui-space-12)}.md\:bui-pl-13{padding-left:var(--bui-space-13)}.md\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pl{padding-left:var(--pl-lg)}.lg\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.lg\:bui-pl-1{padding-left:var(--bui-space-1)}.lg\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.lg\:bui-pl-2{padding-left:var(--bui-space-2)}.lg\:bui-pl-3{padding-left:var(--bui-space-3)}.lg\:bui-pl-4{padding-left:var(--bui-space-4)}.lg\:bui-pl-5{padding-left:var(--bui-space-5)}.lg\:bui-pl-6{padding-left:var(--bui-space-6)}.lg\:bui-pl-7{padding-left:var(--bui-space-7)}.lg\:bui-pl-8{padding-left:var(--bui-space-8)}.lg\:bui-pl-9{padding-left:var(--bui-space-9)}.lg\:bui-pl-10{padding-left:var(--bui-space-10)}.lg\:bui-pl-11{padding-left:var(--bui-space-11)}.lg\:bui-pl-12{padding-left:var(--bui-space-12)}.lg\:bui-pl-13{padding-left:var(--bui-space-13)}.lg\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pl{padding-left:var(--pl-xl)}.xl\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.xl\:bui-pl-1{padding-left:var(--bui-space-1)}.xl\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.xl\:bui-pl-2{padding-left:var(--bui-space-2)}.xl\:bui-pl-3{padding-left:var(--bui-space-3)}.xl\:bui-pl-4{padding-left:var(--bui-space-4)}.xl\:bui-pl-5{padding-left:var(--bui-space-5)}.xl\:bui-pl-6{padding-left:var(--bui-space-6)}.xl\:bui-pl-7{padding-left:var(--bui-space-7)}.xl\:bui-pl-8{padding-left:var(--bui-space-8)}.xl\:bui-pl-9{padding-left:var(--bui-space-9)}.xl\:bui-pl-10{padding-left:var(--bui-space-10)}.xl\:bui-pl-11{padding-left:var(--bui-space-11)}.xl\:bui-pl-12{padding-left:var(--bui-space-12)}.xl\:bui-pl-13{padding-left:var(--bui-space-13)}.xl\:bui-pl-14{padding-left:var(--bui-space-14)}}.bui-pr{padding-right:var(--pr)}.bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.bui-pr-1{padding-right:var(--bui-space-1)}.bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.bui-pr-2{padding-right:var(--bui-space-2)}.bui-pr-3{padding-right:var(--bui-space-3)}.bui-pr-4{padding-right:var(--bui-space-4)}.bui-pr-5{padding-right:var(--bui-space-5)}.bui-pr-6{padding-right:var(--bui-space-6)}.bui-pr-7{padding-right:var(--bui-space-7)}.bui-pr-8{padding-right:var(--bui-space-8)}.bui-pr-9{padding-right:var(--bui-space-9)}.bui-pr-10{padding-right:var(--bui-space-10)}.bui-pr-11{padding-right:var(--bui-space-11)}.bui-pr-12{padding-right:var(--bui-space-12)}.bui-pr-13{padding-right:var(--bui-space-13)}.bui-pr-14{padding-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pr{padding-right:var(--pr-xs)}.xs\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.xs\:bui-pr-1{padding-right:var(--bui-space-1)}.xs\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.xs\:bui-pr-2{padding-right:var(--bui-space-2)}.xs\:bui-pr-3{padding-right:var(--bui-space-3)}.xs\:bui-pr-4{padding-right:var(--bui-space-4)}.xs\:bui-pr-5{padding-right:var(--bui-space-5)}.xs\:bui-pr-6{padding-right:var(--bui-space-6)}.xs\:bui-pr-7{padding-right:var(--bui-space-7)}.xs\:bui-pr-8{padding-right:var(--bui-space-8)}.xs\:bui-pr-9{padding-right:var(--bui-space-9)}.xs\:bui-pr-10{padding-right:var(--bui-space-10)}.xs\:bui-pr-11{padding-right:var(--bui-space-11)}.xs\:bui-pr-12{padding-right:var(--bui-space-12)}.xs\:bui-pr-13{padding-right:var(--bui-space-13)}.xs\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pr{padding-right:var(--pr-sm)}.sm\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.sm\:bui-pr-1{padding-right:var(--bui-space-1)}.sm\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.sm\:bui-pr-2{padding-right:var(--bui-space-2)}.sm\:bui-pr-3{padding-right:var(--bui-space-3)}.sm\:bui-pr-4{padding-right:var(--bui-space-4)}.sm\:bui-pr-5{padding-right:var(--bui-space-5)}.sm\:bui-pr-6{padding-right:var(--bui-space-6)}.sm\:bui-pr-7{padding-right:var(--bui-space-7)}.sm\:bui-pr-8{padding-right:var(--bui-space-8)}.sm\:bui-pr-9{padding-right:var(--bui-space-9)}.sm\:bui-pr-10{padding-right:var(--bui-space-10)}.sm\:bui-pr-11{padding-right:var(--bui-space-11)}.sm\:bui-pr-12{padding-right:var(--bui-space-12)}.sm\:bui-pr-13{padding-right:var(--bui-space-13)}.sm\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pr{padding-right:var(--pr-md)}.md\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.md\:bui-pr-1{padding-right:var(--bui-space-1)}.md\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.md\:bui-pr-2{padding-right:var(--bui-space-2)}.md\:bui-pr-3{padding-right:var(--bui-space-3)}.md\:bui-pr-4{padding-right:var(--bui-space-4)}.md\:bui-pr-5{padding-right:var(--bui-space-5)}.md\:bui-pr-6{padding-right:var(--bui-space-6)}.md\:bui-pr-7{padding-right:var(--bui-space-7)}.md\:bui-pr-8{padding-right:var(--bui-space-8)}.md\:bui-pr-9{padding-right:var(--bui-space-9)}.md\:bui-pr-10{padding-right:var(--bui-space-10)}.md\:bui-pr-11{padding-right:var(--bui-space-11)}.md\:bui-pr-12{padding-right:var(--bui-space-12)}.md\:bui-pr-13{padding-right:var(--bui-space-13)}.md\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pr{padding-right:var(--pr-lg)}.lg\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.lg\:bui-pr-1{padding-right:var(--bui-space-1)}.lg\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.lg\:bui-pr-2{padding-right:var(--bui-space-2)}.lg\:bui-pr-3{padding-right:var(--bui-space-3)}.lg\:bui-pr-4{padding-right:var(--bui-space-4)}.lg\:bui-pr-5{padding-right:var(--bui-space-5)}.lg\:bui-pr-6{padding-right:var(--bui-space-6)}.lg\:bui-pr-7{padding-right:var(--bui-space-7)}.lg\:bui-pr-8{padding-right:var(--bui-space-8)}.lg\:bui-pr-9{padding-right:var(--bui-space-9)}.lg\:bui-pr-10{padding-right:var(--bui-space-10)}.lg\:bui-pr-11{padding-right:var(--bui-space-11)}.lg\:bui-pr-12{padding-right:var(--bui-space-12)}.lg\:bui-pr-13{padding-right:var(--bui-space-13)}.lg\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pr{padding-right:var(--pr-xl)}.xl\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.xl\:bui-pr-1{padding-right:var(--bui-space-1)}.xl\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.xl\:bui-pr-2{padding-right:var(--bui-space-2)}.xl\:bui-pr-3{padding-right:var(--bui-space-3)}.xl\:bui-pr-4{padding-right:var(--bui-space-4)}.xl\:bui-pr-5{padding-right:var(--bui-space-5)}.xl\:bui-pr-6{padding-right:var(--bui-space-6)}.xl\:bui-pr-7{padding-right:var(--bui-space-7)}.xl\:bui-pr-8{padding-right:var(--bui-space-8)}.xl\:bui-pr-9{padding-right:var(--bui-space-9)}.xl\:bui-pr-10{padding-right:var(--bui-space-10)}.xl\:bui-pr-11{padding-right:var(--bui-space-11)}.xl\:bui-pr-12{padding-right:var(--bui-space-12)}.xl\:bui-pr-13{padding-right:var(--bui-space-13)}.xl\:bui-pr-14{padding-right:var(--bui-space-14)}}.bui-pt{padding-top:var(--pt)}.bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.bui-pt-1{padding-top:var(--bui-space-1)}.bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.bui-pt-2{padding-top:var(--bui-space-2)}.bui-pt-3{padding-top:var(--bui-space-3)}.bui-pt-4{padding-top:var(--bui-space-4)}.bui-pt-5{padding-top:var(--bui-space-5)}.bui-pt-6{padding-top:var(--bui-space-6)}.bui-pt-7{padding-top:var(--bui-space-7)}.bui-pt-8{padding-top:var(--bui-space-8)}.bui-pt-9{padding-top:var(--bui-space-9)}.bui-pt-10{padding-top:var(--bui-space-10)}.bui-pt-11{padding-top:var(--bui-space-11)}.bui-pt-12{padding-top:var(--bui-space-12)}.bui-pt-13{padding-top:var(--bui-space-13)}.bui-pt-14{padding-top:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pt{padding-top:var(--pt-xs)}.xs\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.xs\:bui-pt-1{padding-top:var(--bui-space-1)}.xs\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.xs\:bui-pt-2{padding-top:var(--bui-space-2)}.xs\:bui-pt-3{padding-top:var(--bui-space-3)}.xs\:bui-pt-4{padding-top:var(--bui-space-4)}.xs\:bui-pt-5{padding-top:var(--bui-space-5)}.xs\:bui-pt-6{padding-top:var(--bui-space-6)}.xs\:bui-pt-7{padding-top:var(--bui-space-7)}.xs\:bui-pt-8{padding-top:var(--bui-space-8)}.xs\:bui-pt-9{padding-top:var(--bui-space-9)}.xs\:bui-pt-10{padding-top:var(--bui-space-10)}.xs\:bui-pt-11{padding-top:var(--bui-space-11)}.xs\:bui-pt-12{padding-top:var(--bui-space-12)}.xs\:bui-pt-13{padding-top:var(--bui-space-13)}.xs\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pt{padding-top:var(--pt-sm)}.sm\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.sm\:bui-pt-1{padding-top:var(--bui-space-1)}.sm\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.sm\:bui-pt-2{padding-top:var(--bui-space-2)}.sm\:bui-pt-3{padding-top:var(--bui-space-3)}.sm\:bui-pt-4{padding-top:var(--bui-space-4)}.sm\:bui-pt-5{padding-top:var(--bui-space-5)}.sm\:bui-pt-6{padding-top:var(--bui-space-6)}.sm\:bui-pt-7{padding-top:var(--bui-space-7)}.sm\:bui-pt-8{padding-top:var(--bui-space-8)}.sm\:bui-pt-9{padding-top:var(--bui-space-9)}.sm\:bui-pt-10{padding-top:var(--bui-space-10)}.sm\:bui-pt-11{padding-top:var(--bui-space-11)}.sm\:bui-pt-12{padding-top:var(--bui-space-12)}.sm\:bui-pt-13{padding-top:var(--bui-space-13)}.sm\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pt{padding-top:var(--pt-md)}.md\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.md\:bui-pt-1{padding-top:var(--bui-space-1)}.md\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.md\:bui-pt-2{padding-top:var(--bui-space-2)}.md\:bui-pt-3{padding-top:var(--bui-space-3)}.md\:bui-pt-4{padding-top:var(--bui-space-4)}.md\:bui-pt-5{padding-top:var(--bui-space-5)}.md\:bui-pt-6{padding-top:var(--bui-space-6)}.md\:bui-pt-7{padding-top:var(--bui-space-7)}.md\:bui-pt-8{padding-top:var(--bui-space-8)}.md\:bui-pt-9{padding-top:var(--bui-space-9)}.md\:bui-pt-10{padding-top:var(--bui-space-10)}.md\:bui-pt-11{padding-top:var(--bui-space-11)}.md\:bui-pt-12{padding-top:var(--bui-space-12)}.md\:bui-pt-13{padding-top:var(--bui-space-13)}.md\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pt{padding-top:var(--pt-lg)}.lg\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.lg\:bui-pt-1{padding-top:var(--bui-space-1)}.lg\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.lg\:bui-pt-2{padding-top:var(--bui-space-2)}.lg\:bui-pt-3{padding-top:var(--bui-space-3)}.lg\:bui-pt-4{padding-top:var(--bui-space-4)}.lg\:bui-pt-5{padding-top:var(--bui-space-5)}.lg\:bui-pt-6{padding-top:var(--bui-space-6)}.lg\:bui-pt-7{padding-top:var(--bui-space-7)}.lg\:bui-pt-8{padding-top:var(--bui-space-8)}.lg\:bui-pt-9{padding-top:var(--bui-space-9)}.lg\:bui-pt-10{padding-top:var(--bui-space-10)}.lg\:bui-pt-11{padding-top:var(--bui-space-11)}.lg\:bui-pt-12{padding-top:var(--bui-space-12)}.lg\:bui-pt-13{padding-top:var(--bui-space-13)}.lg\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pt{padding-top:var(--pt-xl)}.xl\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.xl\:bui-pt-1{padding-top:var(--bui-space-1)}.xl\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.xl\:bui-pt-2{padding-top:var(--bui-space-2)}.xl\:bui-pt-3{padding-top:var(--bui-space-3)}.xl\:bui-pt-4{padding-top:var(--bui-space-4)}.xl\:bui-pt-5{padding-top:var(--bui-space-5)}.xl\:bui-pt-6{padding-top:var(--bui-space-6)}.xl\:bui-pt-7{padding-top:var(--bui-space-7)}.xl\:bui-pt-8{padding-top:var(--bui-space-8)}.xl\:bui-pt-9{padding-top:var(--bui-space-9)}.xl\:bui-pt-10{padding-top:var(--bui-space-10)}.xl\:bui-pt-11{padding-top:var(--bui-space-11)}.xl\:bui-pt-12{padding-top:var(--bui-space-12)}.xl\:bui-pt-13{padding-top:var(--bui-space-13)}.xl\:bui-pt-14{padding-top:var(--bui-space-14)}}.bui-pb{padding-bottom:var(--pb)}.bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.bui-pb-1{padding-bottom:var(--bui-space-1)}.bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.bui-pb-2{padding-bottom:var(--bui-space-2)}.bui-pb-3{padding-bottom:var(--bui-space-3)}.bui-pb-4{padding-bottom:var(--bui-space-4)}.bui-pb-5{padding-bottom:var(--bui-space-5)}.bui-pb-6{padding-bottom:var(--bui-space-6)}.bui-pb-7{padding-bottom:var(--bui-space-7)}.bui-pb-8{padding-bottom:var(--bui-space-8)}.bui-pb-9{padding-bottom:var(--bui-space-9)}.bui-pb-10{padding-bottom:var(--bui-space-10)}.bui-pb-11{padding-bottom:var(--bui-space-11)}.bui-pb-12{padding-bottom:var(--bui-space-12)}.bui-pb-13{padding-bottom:var(--bui-space-13)}.bui-pb-14{padding-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pb{padding-bottom:var(--pb-xs)}.xs\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.xs\:bui-pb-1{padding-bottom:var(--bui-space-1)}.xs\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.xs\:bui-pb-2{padding-bottom:var(--bui-space-2)}.xs\:bui-pb-3{padding-bottom:var(--bui-space-3)}.xs\:bui-pb-4{padding-bottom:var(--bui-space-4)}.xs\:bui-pb-5{padding-bottom:var(--bui-space-5)}.xs\:bui-pb-6{padding-bottom:var(--bui-space-6)}.xs\:bui-pb-7{padding-bottom:var(--bui-space-7)}.xs\:bui-pb-8{padding-bottom:var(--bui-space-8)}.xs\:bui-pb-9{padding-bottom:var(--bui-space-9)}.xs\:bui-pb-10{padding-bottom:var(--bui-space-10)}.xs\:bui-pb-11{padding-bottom:var(--bui-space-11)}.xs\:bui-pb-12{padding-bottom:var(--bui-space-12)}.xs\:bui-pb-13{padding-bottom:var(--bui-space-13)}.xs\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pb{padding-bottom:var(--pb-sm)}.sm\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.sm\:bui-pb-1{padding-bottom:var(--bui-space-1)}.sm\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.sm\:bui-pb-2{padding-bottom:var(--bui-space-2)}.sm\:bui-pb-3{padding-bottom:var(--bui-space-3)}.sm\:bui-pb-4{padding-bottom:var(--bui-space-4)}.sm\:bui-pb-5{padding-bottom:var(--bui-space-5)}.sm\:bui-pb-6{padding-bottom:var(--bui-space-6)}.sm\:bui-pb-7{padding-bottom:var(--bui-space-7)}.sm\:bui-pb-8{padding-bottom:var(--bui-space-8)}.sm\:bui-pb-9{padding-bottom:var(--bui-space-9)}.sm\:bui-pb-10{padding-bottom:var(--bui-space-10)}.sm\:bui-pb-11{padding-bottom:var(--bui-space-11)}.sm\:bui-pb-12{padding-bottom:var(--bui-space-12)}.sm\:bui-pb-13{padding-bottom:var(--bui-space-13)}.sm\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pb{padding-bottom:var(--pb-md)}.md\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.md\:bui-pb-1{padding-bottom:var(--bui-space-1)}.md\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.md\:bui-pb-2{padding-bottom:var(--bui-space-2)}.md\:bui-pb-3{padding-bottom:var(--bui-space-3)}.md\:bui-pb-4{padding-bottom:var(--bui-space-4)}.md\:bui-pb-5{padding-bottom:var(--bui-space-5)}.md\:bui-pb-6{padding-bottom:var(--bui-space-6)}.md\:bui-pb-7{padding-bottom:var(--bui-space-7)}.md\:bui-pb-8{padding-bottom:var(--bui-space-8)}.md\:bui-pb-9{padding-bottom:var(--bui-space-9)}.md\:bui-pb-10{padding-bottom:var(--bui-space-10)}.md\:bui-pb-11{padding-bottom:var(--bui-space-11)}.md\:bui-pb-12{padding-bottom:var(--bui-space-12)}.md\:bui-pb-13{padding-bottom:var(--bui-space-13)}.md\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pb{padding-bottom:var(--pb-lg)}.lg\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.lg\:bui-pb-1{padding-bottom:var(--bui-space-1)}.lg\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.lg\:bui-pb-2{padding-bottom:var(--bui-space-2)}.lg\:bui-pb-3{padding-bottom:var(--bui-space-3)}.lg\:bui-pb-4{padding-bottom:var(--bui-space-4)}.lg\:bui-pb-5{padding-bottom:var(--bui-space-5)}.lg\:bui-pb-6{padding-bottom:var(--bui-space-6)}.lg\:bui-pb-7{padding-bottom:var(--bui-space-7)}.lg\:bui-pb-8{padding-bottom:var(--bui-space-8)}.lg\:bui-pb-9{padding-bottom:var(--bui-space-9)}.lg\:bui-pb-10{padding-bottom:var(--bui-space-10)}.lg\:bui-pb-11{padding-bottom:var(--bui-space-11)}.lg\:bui-pb-12{padding-bottom:var(--bui-space-12)}.lg\:bui-pb-13{padding-bottom:var(--bui-space-13)}.lg\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pb{padding-bottom:var(--pb-xl)}.xl\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.xl\:bui-pb-1{padding-bottom:var(--bui-space-1)}.xl\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.xl\:bui-pb-2{padding-bottom:var(--bui-space-2)}.xl\:bui-pb-3{padding-bottom:var(--bui-space-3)}.xl\:bui-pb-4{padding-bottom:var(--bui-space-4)}.xl\:bui-pb-5{padding-bottom:var(--bui-space-5)}.xl\:bui-pb-6{padding-bottom:var(--bui-space-6)}.xl\:bui-pb-7{padding-bottom:var(--bui-space-7)}.xl\:bui-pb-8{padding-bottom:var(--bui-space-8)}.xl\:bui-pb-9{padding-bottom:var(--bui-space-9)}.xl\:bui-pb-10{padding-bottom:var(--bui-space-10)}.xl\:bui-pb-11{padding-bottom:var(--bui-space-11)}.xl\:bui-pb-12{padding-bottom:var(--bui-space-12)}.xl\:bui-pb-13{padding-bottom:var(--bui-space-13)}.xl\:bui-pb-14{padding-bottom:var(--bui-space-14)}}.bui-py{padding-top:var(--py);padding-bottom:var(--py)}.bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-py{padding-top:var(--py-xs);padding-bottom:var(--py-xs)}.xs\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.xs\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.xs\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.xs\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.xs\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.xs\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.xs\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.xs\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.xs\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.xs\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.xs\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.xs\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.xs\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.xs\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.xs\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.xs\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-py{padding-top:var(--py-sm);padding-bottom:var(--py-sm)}.sm\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.sm\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.sm\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.sm\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.sm\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.sm\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.sm\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.sm\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.sm\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.sm\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.sm\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.sm\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.sm\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.sm\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.sm\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.sm\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-py{padding-top:var(--py-md);padding-bottom:var(--py-md)}.md\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.md\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.md\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.md\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.md\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.md\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.md\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.md\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.md\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.md\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.md\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.md\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.md\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.md\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.md\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.md\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-py{padding-top:var(--py-lg);padding-bottom:var(--py-lg)}.lg\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.lg\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.lg\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.lg\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.lg\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.lg\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.lg\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.lg\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.lg\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.lg\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.lg\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.lg\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.lg\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.lg\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.lg\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.lg\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-py{padding-top:var(--py-xl);padding-bottom:var(--py-xl)}.xl\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.xl\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.xl\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.xl\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.xl\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.xl\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.xl\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.xl\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.xl\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.xl\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.xl\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.xl\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.xl\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.xl\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.xl\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.xl\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}.bui-px{padding-left:var(--px);padding-right:var(--px)}.bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-px{padding-left:var(--px-xs);padding-right:var(--px-xs)}.xs\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.xs\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.xs\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.xs\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.xs\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.xs\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.xs\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.xs\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.xs\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.xs\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.xs\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.xs\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.xs\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.xs\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.xs\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.xs\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-px{padding-left:var(--px-sm);padding-right:var(--px-sm)}.sm\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.sm\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.sm\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.sm\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.sm\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.sm\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.sm\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.sm\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.sm\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.sm\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.sm\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.sm\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.sm\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.sm\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.sm\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.sm\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-px{padding-left:var(--px-md);padding-right:var(--px-md)}.md\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.md\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.md\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.md\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.md\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.md\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.md\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.md\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.md\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.md\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.md\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.md\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.md\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.md\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.md\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.md\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-px{padding-left:var(--px-lg);padding-right:var(--px-lg)}.lg\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.lg\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.lg\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.lg\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.lg\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.lg\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.lg\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.lg\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.lg\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.lg\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.lg\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.lg\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.lg\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.lg\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.lg\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.lg\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-px{padding-left:var(--px-xl);padding-right:var(--px-xl)}.xl\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.xl\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.xl\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.xl\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.xl\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.xl\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.xl\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.xl\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.xl\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.xl\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.xl\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.xl\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.xl\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.xl\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.xl\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.xl\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}.bui-m{margin:var(--m)}.bui-m-0\.5{margin:var(--bui-space-0_5)}.bui-m-1{margin:var(--bui-space-1)}.bui-m-1\.5{margin:var(--bui-space-1_5)}.bui-m-2{margin:var(--bui-space-2)}.bui-m-3{margin:var(--bui-space-3)}.bui-m-4{margin:var(--bui-space-4)}.bui-m-5{margin:var(--bui-space-5)}.bui-m-6{margin:var(--bui-space-6)}.bui-m-7{margin:var(--bui-space-7)}.bui-m-8{margin:var(--bui-space-8)}.bui-m-9{margin:var(--bui-space-9)}.bui-m-10{margin:var(--bui-space-10)}.bui-m-11{margin:var(--bui-space-11)}.bui-m-12{margin:var(--bui-space-12)}.bui-m-13{margin:var(--bui-space-13)}.bui-m-14{margin:var(--bui-space-14)}@media (width>=640px){.xs\:bui-m{margin:var(--p-xs)}.xs\:bui-m-0\.5{margin:var(--bui-space-0_5)}.xs\:bui-m-1{margin:var(--bui-space-1)}.xs\:bui-m-1\.5{margin:var(--bui-space-1_5)}.xs\:bui-m-2{margin:var(--bui-space-2)}.xs\:bui-m-3{margin:var(--bui-space-3)}.xs\:bui-m-4{margin:var(--bui-space-4)}.xs\:bui-m-5{margin:var(--bui-space-5)}.xs\:bui-m-6{margin:var(--bui-space-6)}.xs\:bui-m-7{margin:var(--bui-space-7)}.xs\:bui-m-8{margin:var(--bui-space-8)}.xs\:bui-m-9{margin:var(--bui-space-9)}.xs\:bui-m-10{margin:var(--bui-space-10)}.xs\:bui-m-11{margin:var(--bui-space-11)}.xs\:bui-m-12{margin:var(--bui-space-12)}.xs\:bui-m-13{margin:var(--bui-space-13)}.xs\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-m{margin:var(--p-sm)}.sm\:bui-m-0\.5{margin:var(--bui-space-0_5)}.sm\:bui-m-1{margin:var(--bui-space-1)}.sm\:bui-m-1\.5{margin:var(--bui-space-1_5)}.sm\:bui-m-2{margin:var(--bui-space-2)}.sm\:bui-m-3{margin:var(--bui-space-3)}.sm\:bui-m-4{margin:var(--bui-space-4)}.sm\:bui-m-5{margin:var(--bui-space-5)}.sm\:bui-m-6{margin:var(--bui-space-6)}.sm\:bui-m-7{margin:var(--bui-space-7)}.sm\:bui-m-8{margin:var(--bui-space-8)}.sm\:bui-m-9{margin:var(--bui-space-9)}.sm\:bui-m-10{margin:var(--bui-space-10)}.sm\:bui-m-11{margin:var(--bui-space-11)}.sm\:bui-m-12{margin:var(--bui-space-12)}.sm\:bui-m-13{margin:var(--bui-space-13)}.sm\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-m{margin:var(--p-md)}.md\:bui-m-0\.5{margin:var(--bui-space-0_5)}.md\:bui-m-1{margin:var(--bui-space-1)}.md\:bui-m-1\.5{margin:var(--bui-space-1_5)}.md\:bui-m-2{margin:var(--bui-space-2)}.md\:bui-m-3{margin:var(--bui-space-3)}.md\:bui-m-4{margin:var(--bui-space-4)}.md\:bui-m-5{margin:var(--bui-space-5)}.md\:bui-m-6{margin:var(--bui-space-6)}.md\:bui-m-7{margin:var(--bui-space-7)}.md\:bui-m-8{margin:var(--bui-space-8)}.md\:bui-m-9{margin:var(--bui-space-9)}.md\:bui-m-10{margin:var(--bui-space-10)}.md\:bui-m-11{margin:var(--bui-space-11)}.md\:bui-m-12{margin:var(--bui-space-12)}.md\:bui-m-13{margin:var(--bui-space-13)}.md\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-m{margin:var(--p-lg)}.lg\:bui-m-0\.5{margin:var(--bui-space-0_5)}.lg\:bui-m-1{margin:var(--bui-space-1)}.lg\:bui-m-1\.5{margin:var(--bui-space-1_5)}.lg\:bui-m-2{margin:var(--bui-space-2)}.lg\:bui-m-3{margin:var(--bui-space-3)}.lg\:bui-m-4{margin:var(--bui-space-4)}.lg\:bui-m-5{margin:var(--bui-space-5)}.lg\:bui-m-6{margin:var(--bui-space-6)}.lg\:bui-m-7{margin:var(--bui-space-7)}.lg\:bui-m-8{margin:var(--bui-space-8)}.lg\:bui-m-9{margin:var(--bui-space-9)}.lg\:bui-m-10{margin:var(--bui-space-10)}.lg\:bui-m-11{margin:var(--bui-space-11)}.lg\:bui-m-12{margin:var(--bui-space-12)}.lg\:bui-m-13{margin:var(--bui-space-13)}.lg\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-m{margin:var(--p-xl)}.xl\:bui-m-0\.5{margin:var(--bui-space-0_5)}.xl\:bui-m-1{margin:var(--bui-space-1)}.xl\:bui-m-1\.5{margin:var(--bui-space-1_5)}.xl\:bui-m-2{margin:var(--bui-space-2)}.xl\:bui-m-3{margin:var(--bui-space-3)}.xl\:bui-m-4{margin:var(--bui-space-4)}.xl\:bui-m-5{margin:var(--bui-space-5)}.xl\:bui-m-6{margin:var(--bui-space-6)}.xl\:bui-m-7{margin:var(--bui-space-7)}.xl\:bui-m-8{margin:var(--bui-space-8)}.xl\:bui-m-9{margin:var(--bui-space-9)}.xl\:bui-m-10{margin:var(--bui-space-10)}.xl\:bui-m-11{margin:var(--bui-space-11)}.xl\:bui-m-12{margin:var(--bui-space-12)}.xl\:bui-m-13{margin:var(--bui-space-13)}.xl\:bui-m-14{margin:var(--bui-space-14)}}.bui-ml{margin-left:var(--ml)}.bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.bui-ml-1{margin-left:var(--bui-space-1)}.bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.bui-ml-2{margin-left:var(--bui-space-2)}.bui-ml-3{margin-left:var(--bui-space-3)}.bui-ml-4{margin-left:var(--bui-space-4)}.bui-ml-5{margin-left:var(--bui-space-5)}.bui-ml-6{margin-left:var(--bui-space-6)}.bui-ml-7{margin-left:var(--bui-space-7)}.bui-ml-8{margin-left:var(--bui-space-8)}.bui-ml-9{margin-left:var(--bui-space-9)}.bui-ml-10{margin-left:var(--bui-space-10)}.bui-ml-11{margin-left:var(--bui-space-11)}.bui-ml-12{margin-left:var(--bui-space-12)}.bui-ml-13{margin-left:var(--bui-space-13)}.bui-ml-14{margin-left:var(--bui-space-14)}@media (width>=640px){.xs\:bui-ml{margin-left:var(--ml-xs)}.xs\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.xs\:bui-ml-1{margin-left:var(--bui-space-1)}.xs\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.xs\:bui-ml-2{margin-left:var(--bui-space-2)}.xs\:bui-ml-3{margin-left:var(--bui-space-3)}.xs\:bui-ml-4{margin-left:var(--bui-space-4)}.xs\:bui-ml-5{margin-left:var(--bui-space-5)}.xs\:bui-ml-6{margin-left:var(--bui-space-6)}.xs\:bui-ml-7{margin-left:var(--bui-space-7)}.xs\:bui-ml-8{margin-left:var(--bui-space-8)}.xs\:bui-ml-9{margin-left:var(--bui-space-9)}.xs\:bui-ml-10{margin-left:var(--bui-space-10)}.xs\:bui-ml-11{margin-left:var(--bui-space-11)}.xs\:bui-ml-12{margin-left:var(--bui-space-12)}.xs\:bui-ml-13{margin-left:var(--bui-space-13)}.xs\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-ml{margin-left:var(--ml-sm)}.sm\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.sm\:bui-ml-1{margin-left:var(--bui-space-1)}.sm\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.sm\:bui-ml-2{margin-left:var(--bui-space-2)}.sm\:bui-ml-3{margin-left:var(--bui-space-3)}.sm\:bui-ml-4{margin-left:var(--bui-space-4)}.sm\:bui-ml-5{margin-left:var(--bui-space-5)}.sm\:bui-ml-6{margin-left:var(--bui-space-6)}.sm\:bui-ml-7{margin-left:var(--bui-space-7)}.sm\:bui-ml-8{margin-left:var(--bui-space-8)}.sm\:bui-ml-9{margin-left:var(--bui-space-9)}.sm\:bui-ml-10{margin-left:var(--bui-space-10)}.sm\:bui-ml-11{margin-left:var(--bui-space-11)}.sm\:bui-ml-12{margin-left:var(--bui-space-12)}.sm\:bui-ml-13{margin-left:var(--bui-space-13)}.sm\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-ml{margin-left:var(--ml-md)}.md\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.md\:bui-ml-1{margin-left:var(--bui-space-1)}.md\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.md\:bui-ml-2{margin-left:var(--bui-space-2)}.md\:bui-ml-3{margin-left:var(--bui-space-3)}.md\:bui-ml-4{margin-left:var(--bui-space-4)}.md\:bui-ml-5{margin-left:var(--bui-space-5)}.md\:bui-ml-6{margin-left:var(--bui-space-6)}.md\:bui-ml-7{margin-left:var(--bui-space-7)}.md\:bui-ml-8{margin-left:var(--bui-space-8)}.md\:bui-ml-9{margin-left:var(--bui-space-9)}.md\:bui-ml-10{margin-left:var(--bui-space-10)}.md\:bui-ml-11{margin-left:var(--bui-space-11)}.md\:bui-ml-12{margin-left:var(--bui-space-12)}.md\:bui-ml-13{margin-left:var(--bui-space-13)}.md\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-ml{margin-left:var(--ml-lg)}.lg\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.lg\:bui-ml-1{margin-left:var(--bui-space-1)}.lg\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.lg\:bui-ml-2{margin-left:var(--bui-space-2)}.lg\:bui-ml-3{margin-left:var(--bui-space-3)}.lg\:bui-ml-4{margin-left:var(--bui-space-4)}.lg\:bui-ml-5{margin-left:var(--bui-space-5)}.lg\:bui-ml-6{margin-left:var(--bui-space-6)}.lg\:bui-ml-7{margin-left:var(--bui-space-7)}.lg\:bui-ml-8{margin-left:var(--bui-space-8)}.lg\:bui-ml-9{margin-left:var(--bui-space-9)}.lg\:bui-ml-10{margin-left:var(--bui-space-10)}.lg\:bui-ml-11{margin-left:var(--bui-space-11)}.lg\:bui-ml-12{margin-left:var(--bui-space-12)}.lg\:bui-ml-13{margin-left:var(--bui-space-13)}.lg\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-ml{margin-left:var(--ml-xl)}.xl\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.xl\:bui-ml-1{margin-left:var(--bui-space-1)}.xl\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.xl\:bui-ml-2{margin-left:var(--bui-space-2)}.xl\:bui-ml-3{margin-left:var(--bui-space-3)}.xl\:bui-ml-4{margin-left:var(--bui-space-4)}.xl\:bui-ml-5{margin-left:var(--bui-space-5)}.xl\:bui-ml-6{margin-left:var(--bui-space-6)}.xl\:bui-ml-7{margin-left:var(--bui-space-7)}.xl\:bui-ml-8{margin-left:var(--bui-space-8)}.xl\:bui-ml-9{margin-left:var(--bui-space-9)}.xl\:bui-ml-10{margin-left:var(--bui-space-10)}.xl\:bui-ml-11{margin-left:var(--bui-space-11)}.xl\:bui-ml-12{margin-left:var(--bui-space-12)}.xl\:bui-ml-13{margin-left:var(--bui-space-13)}.xl\:bui-ml-14{margin-left:var(--bui-space-14)}}.bui-mr{margin-right:var(--mr)}.bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.bui-mr-1{margin-right:var(--bui-space-1)}.bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.bui-mr-2{margin-right:var(--bui-space-2)}.bui-mr-3{margin-right:var(--bui-space-3)}.bui-mr-4{margin-right:var(--bui-space-4)}.bui-mr-5{margin-right:var(--bui-space-5)}.bui-mr-6{margin-right:var(--bui-space-6)}.bui-mr-7{margin-right:var(--bui-space-7)}.bui-mr-8{margin-right:var(--bui-space-8)}.bui-mr-9{margin-right:var(--bui-space-9)}.bui-mr-10{margin-right:var(--bui-space-10)}.bui-mr-11{margin-right:var(--bui-space-11)}.bui-mr-12{margin-right:var(--bui-space-12)}.bui-mr-13{margin-right:var(--bui-space-13)}.bui-mr-14{margin-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mr{margin-right:var(--mr-xs)}.xs\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.xs\:bui-mr-1{margin-right:var(--bui-space-1)}.xs\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.xs\:bui-mr-2{margin-right:var(--bui-space-2)}.xs\:bui-mr-3{margin-right:var(--bui-space-3)}.xs\:bui-mr-4{margin-right:var(--bui-space-4)}.xs\:bui-mr-5{margin-right:var(--bui-space-5)}.xs\:bui-mr-6{margin-right:var(--bui-space-6)}.xs\:bui-mr-7{margin-right:var(--bui-space-7)}.xs\:bui-mr-8{margin-right:var(--bui-space-8)}.xs\:bui-mr-9{margin-right:var(--bui-space-9)}.xs\:bui-mr-10{margin-right:var(--bui-space-10)}.xs\:bui-mr-11{margin-right:var(--bui-space-11)}.xs\:bui-mr-12{margin-right:var(--bui-space-12)}.xs\:bui-mr-13{margin-right:var(--bui-space-13)}.xs\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mr{margin-right:var(--mr-sm)}.sm\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.sm\:bui-mr-1{margin-right:var(--bui-space-1)}.sm\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.sm\:bui-mr-2{margin-right:var(--bui-space-2)}.sm\:bui-mr-3{margin-right:var(--bui-space-3)}.sm\:bui-mr-4{margin-right:var(--bui-space-4)}.sm\:bui-mr-5{margin-right:var(--bui-space-5)}.sm\:bui-mr-6{margin-right:var(--bui-space-6)}.sm\:bui-mr-7{margin-right:var(--bui-space-7)}.sm\:bui-mr-8{margin-right:var(--bui-space-8)}.sm\:bui-mr-9{margin-right:var(--bui-space-9)}.sm\:bui-mr-10{margin-right:var(--bui-space-10)}.sm\:bui-mr-11{margin-right:var(--bui-space-11)}.sm\:bui-mr-12{margin-right:var(--bui-space-12)}.sm\:bui-mr-13{margin-right:var(--bui-space-13)}.sm\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mr{margin-right:var(--mr-md)}.md\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.md\:bui-mr-1{margin-right:var(--bui-space-1)}.md\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.md\:bui-mr-2{margin-right:var(--bui-space-2)}.md\:bui-mr-3{margin-right:var(--bui-space-3)}.md\:bui-mr-4{margin-right:var(--bui-space-4)}.md\:bui-mr-5{margin-right:var(--bui-space-5)}.md\:bui-mr-6{margin-right:var(--bui-space-6)}.md\:bui-mr-7{margin-right:var(--bui-space-7)}.md\:bui-mr-8{margin-right:var(--bui-space-8)}.md\:bui-mr-9{margin-right:var(--bui-space-9)}.md\:bui-mr-10{margin-right:var(--bui-space-10)}.md\:bui-mr-11{margin-right:var(--bui-space-11)}.md\:bui-mr-12{margin-right:var(--bui-space-12)}.md\:bui-mr-13{margin-right:var(--bui-space-13)}.md\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mr{margin-right:var(--mr-lg)}.lg\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.lg\:bui-mr-1{margin-right:var(--bui-space-1)}.lg\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.lg\:bui-mr-2{margin-right:var(--bui-space-2)}.lg\:bui-mr-3{margin-right:var(--bui-space-3)}.lg\:bui-mr-4{margin-right:var(--bui-space-4)}.lg\:bui-mr-5{margin-right:var(--bui-space-5)}.lg\:bui-mr-6{margin-right:var(--bui-space-6)}.lg\:bui-mr-7{margin-right:var(--bui-space-7)}.lg\:bui-mr-8{margin-right:var(--bui-space-8)}.lg\:bui-mr-9{margin-right:var(--bui-space-9)}.lg\:bui-mr-10{margin-right:var(--bui-space-10)}.lg\:bui-mr-11{margin-right:var(--bui-space-11)}.lg\:bui-mr-12{margin-right:var(--bui-space-12)}.lg\:bui-mr-13{margin-right:var(--bui-space-13)}.lg\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mr{margin-right:var(--mr-xl)}.xl\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.xl\:bui-mr-1{margin-right:var(--bui-space-1)}.xl\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.xl\:bui-mr-2{margin-right:var(--bui-space-2)}.xl\:bui-mr-3{margin-right:var(--bui-space-3)}.xl\:bui-mr-4{margin-right:var(--bui-space-4)}.xl\:bui-mr-5{margin-right:var(--bui-space-5)}.xl\:bui-mr-6{margin-right:var(--bui-space-6)}.xl\:bui-mr-7{margin-right:var(--bui-space-7)}.xl\:bui-mr-8{margin-right:var(--bui-space-8)}.xl\:bui-mr-9{margin-right:var(--bui-space-9)}.xl\:bui-mr-10{margin-right:var(--bui-space-10)}.xl\:bui-mr-11{margin-right:var(--bui-space-11)}.xl\:bui-mr-12{margin-right:var(--bui-space-12)}.xl\:bui-mr-13{margin-right:var(--bui-space-13)}.xl\:bui-mr-14{margin-right:var(--bui-space-14)}}.bui-mt{margin-top:var(--mt)}.bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.bui-mt-1{margin-top:var(--bui-space-1)}.bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.bui-mt-2{margin-top:var(--bui-space-2)}.bui-mt-3{margin-top:var(--bui-space-3)}.bui-mt-4{margin-top:var(--bui-space-4)}.bui-mt-5{margin-top:var(--bui-space-5)}.bui-mt-6{margin-top:var(--bui-space-6)}.bui-mt-7{margin-top:var(--bui-space-7)}.bui-mt-8{margin-top:var(--bui-space-8)}.bui-mt-9{margin-top:var(--bui-space-9)}.bui-mt-10{margin-top:var(--bui-space-10)}.bui-mt-11{margin-top:var(--bui-space-11)}.bui-mt-12{margin-top:var(--bui-space-12)}.bui-mt-13{margin-top:var(--bui-space-13)}.bui-mt-14{margin-top:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mt{margin-top:var(--mt-xs)}.xs\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.xs\:bui-mt-1{margin-top:var(--bui-space-1)}.xs\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.xs\:bui-mt-2{margin-top:var(--bui-space-2)}.xs\:bui-mt-3{margin-top:var(--bui-space-3)}.xs\:bui-mt-4{margin-top:var(--bui-space-4)}.xs\:bui-mt-5{margin-top:var(--bui-space-5)}.xs\:bui-mt-6{margin-top:var(--bui-space-6)}.xs\:bui-mt-7{margin-top:var(--bui-space-7)}.xs\:bui-mt-8{margin-top:var(--bui-space-8)}.xs\:bui-mt-9{margin-top:var(--bui-space-9)}.xs\:bui-mt-10{margin-top:var(--bui-space-10)}.xs\:bui-mt-11{margin-top:var(--bui-space-11)}.xs\:bui-mt-12{margin-top:var(--bui-space-12)}.xs\:bui-mt-13{margin-top:var(--bui-space-13)}.xs\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mt{margin-top:var(--mt-sm)}.sm\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.sm\:bui-mt-1{margin-top:var(--bui-space-1)}.sm\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.sm\:bui-mt-2{margin-top:var(--bui-space-2)}.sm\:bui-mt-3{margin-top:var(--bui-space-3)}.sm\:bui-mt-4{margin-top:var(--bui-space-4)}.sm\:bui-mt-5{margin-top:var(--bui-space-5)}.sm\:bui-mt-6{margin-top:var(--bui-space-6)}.sm\:bui-mt-7{margin-top:var(--bui-space-7)}.sm\:bui-mt-8{margin-top:var(--bui-space-8)}.sm\:bui-mt-9{margin-top:var(--bui-space-9)}.sm\:bui-mt-10{margin-top:var(--bui-space-10)}.sm\:bui-mt-11{margin-top:var(--bui-space-11)}.sm\:bui-mt-12{margin-top:var(--bui-space-12)}.sm\:bui-mt-13{margin-top:var(--bui-space-13)}.sm\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mt{margin-top:var(--mt-md)}.md\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.md\:bui-mt-1{margin-top:var(--bui-space-1)}.md\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.md\:bui-mt-2{margin-top:var(--bui-space-2)}.md\:bui-mt-3{margin-top:var(--bui-space-3)}.md\:bui-mt-4{margin-top:var(--bui-space-4)}.md\:bui-mt-5{margin-top:var(--bui-space-5)}.md\:bui-mt-6{margin-top:var(--bui-space-6)}.md\:bui-mt-7{margin-top:var(--bui-space-7)}.md\:bui-mt-8{margin-top:var(--bui-space-8)}.md\:bui-mt-9{margin-top:var(--bui-space-9)}.md\:bui-mt-10{margin-top:var(--bui-space-10)}.md\:bui-mt-11{margin-top:var(--bui-space-11)}.md\:bui-mt-12{margin-top:var(--bui-space-12)}.md\:bui-mt-13{margin-top:var(--bui-space-13)}.md\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mt{margin-top:var(--mt-lg)}.lg\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.lg\:bui-mt-1{margin-top:var(--bui-space-1)}.lg\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.lg\:bui-mt-2{margin-top:var(--bui-space-2)}.lg\:bui-mt-3{margin-top:var(--bui-space-3)}.lg\:bui-mt-4{margin-top:var(--bui-space-4)}.lg\:bui-mt-5{margin-top:var(--bui-space-5)}.lg\:bui-mt-6{margin-top:var(--bui-space-6)}.lg\:bui-mt-7{margin-top:var(--bui-space-7)}.lg\:bui-mt-8{margin-top:var(--bui-space-8)}.lg\:bui-mt-9{margin-top:var(--bui-space-9)}.lg\:bui-mt-10{margin-top:var(--bui-space-10)}.lg\:bui-mt-11{margin-top:var(--bui-space-11)}.lg\:bui-mt-12{margin-top:var(--bui-space-12)}.lg\:bui-mt-13{margin-top:var(--bui-space-13)}.lg\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mt{margin-top:var(--mt-xl)}.xl\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.xl\:bui-mt-1{margin-top:var(--bui-space-1)}.xl\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.xl\:bui-mt-2{margin-top:var(--bui-space-2)}.xl\:bui-mt-3{margin-top:var(--bui-space-3)}.xl\:bui-mt-4{margin-top:var(--bui-space-4)}.xl\:bui-mt-5{margin-top:var(--bui-space-5)}.xl\:bui-mt-6{margin-top:var(--bui-space-6)}.xl\:bui-mt-7{margin-top:var(--bui-space-7)}.xl\:bui-mt-8{margin-top:var(--bui-space-8)}.xl\:bui-mt-9{margin-top:var(--bui-space-9)}.xl\:bui-mt-10{margin-top:var(--bui-space-10)}.xl\:bui-mt-11{margin-top:var(--bui-space-11)}.xl\:bui-mt-12{margin-top:var(--bui-space-12)}.xl\:bui-mt-13{margin-top:var(--bui-space-13)}.xl\:bui-mt-14{margin-top:var(--bui-space-14)}}.bui-mb{margin-bottom:var(--mb)}.bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.bui-mb-1{margin-bottom:var(--bui-space-1)}.bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.bui-mb-2{margin-bottom:var(--bui-space-2)}.bui-mb-3{margin-bottom:var(--bui-space-3)}.bui-mb-4{margin-bottom:var(--bui-space-4)}.bui-mb-5{margin-bottom:var(--bui-space-5)}.bui-mb-6{margin-bottom:var(--bui-space-6)}.bui-mb-7{margin-bottom:var(--bui-space-7)}.bui-mb-8{margin-bottom:var(--bui-space-8)}.bui-mb-9{margin-bottom:var(--bui-space-9)}.bui-mb-10{margin-bottom:var(--bui-space-10)}.bui-mb-11{margin-bottom:var(--bui-space-11)}.bui-mb-12{margin-bottom:var(--bui-space-12)}.bui-mb-13{margin-bottom:var(--bui-space-13)}.bui-mb-14{margin-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mb{margin-bottom:var(--mb-xs)}.xs\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.xs\:bui-mb-1{margin-bottom:var(--bui-space-1)}.xs\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.xs\:bui-mb-2{margin-bottom:var(--bui-space-2)}.xs\:bui-mb-3{margin-bottom:var(--bui-space-3)}.xs\:bui-mb-4{margin-bottom:var(--bui-space-4)}.xs\:bui-mb-5{margin-bottom:var(--bui-space-5)}.xs\:bui-mb-6{margin-bottom:var(--bui-space-6)}.xs\:bui-mb-7{margin-bottom:var(--bui-space-7)}.xs\:bui-mb-8{margin-bottom:var(--bui-space-8)}.xs\:bui-mb-9{margin-bottom:var(--bui-space-9)}.xs\:bui-mb-10{margin-bottom:var(--bui-space-10)}.xs\:bui-mb-11{margin-bottom:var(--bui-space-11)}.xs\:bui-mb-12{margin-bottom:var(--bui-space-12)}.xs\:bui-mb-13{margin-bottom:var(--bui-space-13)}.xs\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mb{margin-bottom:var(--mb-sm)}.sm\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.sm\:bui-mb-1{margin-bottom:var(--bui-space-1)}.sm\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.sm\:bui-mb-2{margin-bottom:var(--bui-space-2)}.sm\:bui-mb-3{margin-bottom:var(--bui-space-3)}.sm\:bui-mb-4{margin-bottom:var(--bui-space-4)}.sm\:bui-mb-5{margin-bottom:var(--bui-space-5)}.sm\:bui-mb-6{margin-bottom:var(--bui-space-6)}.sm\:bui-mb-7{margin-bottom:var(--bui-space-7)}.sm\:bui-mb-8{margin-bottom:var(--bui-space-8)}.sm\:bui-mb-9{margin-bottom:var(--bui-space-9)}.sm\:bui-mb-10{margin-bottom:var(--bui-space-10)}.sm\:bui-mb-11{margin-bottom:var(--bui-space-11)}.sm\:bui-mb-12{margin-bottom:var(--bui-space-12)}.sm\:bui-mb-13{margin-bottom:var(--bui-space-13)}.sm\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mb{margin-bottom:var(--mb-md)}.md\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.md\:bui-mb-1{margin-bottom:var(--bui-space-1)}.md\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.md\:bui-mb-2{margin-bottom:var(--bui-space-2)}.md\:bui-mb-3{margin-bottom:var(--bui-space-3)}.md\:bui-mb-4{margin-bottom:var(--bui-space-4)}.md\:bui-mb-5{margin-bottom:var(--bui-space-5)}.md\:bui-mb-6{margin-bottom:var(--bui-space-6)}.md\:bui-mb-7{margin-bottom:var(--bui-space-7)}.md\:bui-mb-8{margin-bottom:var(--bui-space-8)}.md\:bui-mb-9{margin-bottom:var(--bui-space-9)}.md\:bui-mb-10{margin-bottom:var(--bui-space-10)}.md\:bui-mb-11{margin-bottom:var(--bui-space-11)}.md\:bui-mb-12{margin-bottom:var(--bui-space-12)}.md\:bui-mb-13{margin-bottom:var(--bui-space-13)}.md\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mb{margin-bottom:var(--mb-lg)}.lg\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.lg\:bui-mb-1{margin-bottom:var(--bui-space-1)}.lg\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.lg\:bui-mb-2{margin-bottom:var(--bui-space-2)}.lg\:bui-mb-3{margin-bottom:var(--bui-space-3)}.lg\:bui-mb-4{margin-bottom:var(--bui-space-4)}.lg\:bui-mb-5{margin-bottom:var(--bui-space-5)}.lg\:bui-mb-6{margin-bottom:var(--bui-space-6)}.lg\:bui-mb-7{margin-bottom:var(--bui-space-7)}.lg\:bui-mb-8{margin-bottom:var(--bui-space-8)}.lg\:bui-mb-9{margin-bottom:var(--bui-space-9)}.lg\:bui-mb-10{margin-bottom:var(--bui-space-10)}.lg\:bui-mb-11{margin-bottom:var(--bui-space-11)}.lg\:bui-mb-12{margin-bottom:var(--bui-space-12)}.lg\:bui-mb-13{margin-bottom:var(--bui-space-13)}.lg\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mb{margin-bottom:var(--mb-xl)}.xl\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.xl\:bui-mb-1{margin-bottom:var(--bui-space-1)}.xl\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.xl\:bui-mb-2{margin-bottom:var(--bui-space-2)}.xl\:bui-mb-3{margin-bottom:var(--bui-space-3)}.xl\:bui-mb-4{margin-bottom:var(--bui-space-4)}.xl\:bui-mb-5{margin-bottom:var(--bui-space-5)}.xl\:bui-mb-6{margin-bottom:var(--bui-space-6)}.xl\:bui-mb-7{margin-bottom:var(--bui-space-7)}.xl\:bui-mb-8{margin-bottom:var(--bui-space-8)}.xl\:bui-mb-9{margin-bottom:var(--bui-space-9)}.xl\:bui-mb-10{margin-bottom:var(--bui-space-10)}.xl\:bui-mb-11{margin-bottom:var(--bui-space-11)}.xl\:bui-mb-12{margin-bottom:var(--bui-space-12)}.xl\:bui-mb-13{margin-bottom:var(--bui-space-13)}.xl\:bui-mb-14{margin-bottom:var(--bui-space-14)}}.bui-my{margin-top:var(--my);margin-bottom:var(--my)}.bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-my{margin-top:var(--my-xs);margin-bottom:var(--my-xs)}.xs\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.xs\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.xs\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.xs\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.xs\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.xs\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.xs\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.xs\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.xs\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.xs\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.xs\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.xs\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.xs\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.xs\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.xs\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.xs\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-my{margin-top:var(--my-sm);margin-bottom:var(--my-sm)}.sm\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.sm\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.sm\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.sm\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.sm\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.sm\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.sm\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.sm\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.sm\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.sm\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.sm\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.sm\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.sm\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.sm\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.sm\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.sm\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-my{margin-top:var(--my-md);margin-bottom:var(--my-md)}.md\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.md\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.md\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.md\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.md\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.md\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.md\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.md\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.md\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.md\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.md\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.md\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.md\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.md\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.md\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.md\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-my{margin-top:var(--my-lg);margin-bottom:var(--my-lg)}.lg\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.lg\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.lg\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.lg\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.lg\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.lg\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.lg\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.lg\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.lg\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.lg\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.lg\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.lg\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.lg\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.lg\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.lg\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.lg\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-my{margin-top:var(--my-xl);margin-bottom:var(--my-xl)}.xl\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.xl\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.xl\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.xl\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.xl\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.xl\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.xl\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.xl\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.xl\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.xl\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.xl\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.xl\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.xl\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.xl\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.xl\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.xl\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}.bui-mx{margin-left:var(--mx);margin-right:var(--mx)}.bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mx{margin-left:var(--mx-xs);margin-right:var(--mx-xs)}.xs\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.xs\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.xs\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.xs\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.xs\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.xs\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.xs\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.xs\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.xs\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.xs\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.xs\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.xs\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.xs\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.xs\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.xs\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.xs\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mx{margin-left:var(--mx-sm);margin-right:var(--mx-sm)}.sm\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.sm\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.sm\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.sm\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.sm\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.sm\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.sm\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.sm\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.sm\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.sm\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.sm\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.sm\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.sm\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.sm\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.sm\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.sm\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mx{margin-left:var(--mx-md);margin-right:var(--mx-md)}.md\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.md\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.md\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.md\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.md\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.md\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.md\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.md\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.md\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.md\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.md\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.md\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.md\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.md\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.md\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.md\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mx{margin-left:var(--mx-lg);margin-right:var(--mx-lg)}.lg\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.lg\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.lg\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.lg\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.lg\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.lg\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.lg\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.lg\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.lg\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.lg\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.lg\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.lg\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.lg\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.lg\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.lg\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.lg\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mx{margin-left:var(--mx-xl);margin-right:var(--mx-xl)}.xl\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.xl\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.xl\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.xl\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.xl\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.xl\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.xl\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.xl\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.xl\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.xl\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.xl\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.xl\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.xl\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.xl\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.xl\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.xl\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}.bui-display-none{display:none}.bui-display-inline{display:inline}.bui-display-inline-block{display:inline-block}.bui-display-block{display:block}@media (width>=640px){.xs\:bui-display-none{display:none}.xs\:bui-display-inline{display:inline}.xs\:bui-display-inline-block{display:inline-block}.xs\:bui-display-block{display:block}}@media (width>=768px){.sm\:bui-display-none{display:none}.sm\:bui-display-inline{display:inline}.sm\:bui-display-inline-block{display:inline-block}.sm\:bui-display-block{display:block}}@media (width>=1024px){.md\:bui-display-none{display:none}.md\:bui-display-inline{display:inline}.md\:bui-display-inline-block{display:inline-block}.md\:bui-display-block{display:block}}@media (width>=1280px){.lg\:bui-display-none{display:none}.lg\:bui-display-inline{display:inline}.lg\:bui-display-inline-block{display:inline-block}.lg\:bui-display-block{display:block}}@media (width>=1536px){.xl\:bui-display-none{display:none}.xl\:bui-display-inline{display:inline}.xl\:bui-display-inline-block{display:inline-block}.xl\:bui-display-block{display:block}}.bui-w{width:var(--width)}.bui-min-w{min-width:var(--min-width)}.bui-max-w{max-width:var(--max-width)}@media (width>=640px){.xs\:bui-w{width:var(--width)}.xs\:bui-min-w{min-width:var(--min-width)}.xs\:bui-max-w{max-width:var(--max-width)}}@media (width>=768px){.sm\:bui-w{width:var(--width)}.sm\:bui-min-w{min-width:var(--min-width)}.sm\:bui-max-w{max-width:var(--max-width)}}@media (width>=1024px){.md\:bui-w{width:var(--width)}.md\:bui-min-w{min-width:var(--min-width)}.md\:bui-max-w{max-width:var(--max-width)}}@media (width>=1280px){.lg\:bui-w{width:var(--width)}.lg\:bui-min-w{min-width:var(--min-width)}.lg\:bui-max-w{max-width:var(--max-width)}}@media (width>=1536px){.xl\:bui-w{width:var(--width)}.xl\:bui-min-w{min-width:var(--min-width)}.xl\:bui-max-w{max-width:var(--max-width)}}.bui-h{height:var(--height)}.bui-min-h{min-height:var(--min-height)}.bui-max-h{max-height:var(--max-height)}@media (width>=640px){.xs\:bui-h{height:var(--height)}.xs\:bui-min-h{min-height:var(--min-height)}.xs\:bui-max-h{max-height:var(--max-height)}}@media (width>=768px){.sm\:bui-h{height:var(--height)}.sm\:bui-min-h{min-height:var(--min-height)}.sm\:bui-max-h{max-height:var(--max-height)}}@media (width>=1024px){.md\:bui-h{height:var(--height)}.md\:bui-min-h{min-height:var(--min-height)}.md\:bui-max-h{max-height:var(--max-height)}}@media (width>=1280px){.lg\:bui-h{height:var(--height)}.lg\:bui-min-h{min-height:var(--min-height)}.lg\:bui-max-h{max-height:var(--max-height)}}@media (width>=1536px){.xl\:bui-h{height:var(--height)}.xl\:bui-min-h{min-height:var(--min-height)}.xl\:bui-max-h{max-height:var(--max-height)}}.bui-position-absolute{position:absolute}.bui-position-fixed{position:fixed}.bui-position-sticky{position:sticky}.bui-position-relative{position:relative}.bui-position-static{position:static}@media (width>=640px){.xs\:bui-position-absolute{position:absolute}.xs\:bui-position-fixed{position:fixed}.xs\:bui-position-sticky{position:sticky}.xs\:bui-position-relative{position:relative}.xs\:bui-position-static{position:static}}@media (width>=768px){.sm\:bui-position-absolute{position:absolute}.sm\:bui-position-fixed{position:fixed}.sm\:bui-position-sticky{position:sticky}.sm\:bui-position-relative{position:relative}.sm\:bui-position-static{position:static}}@media (width>=1024px){.md\:bui-position-absolute{position:absolute}.md\:bui-position-fixed{position:fixed}.md\:bui-position-sticky{position:sticky}.md\:bui-position-relative{position:relative}.md\:bui-position-static{position:static}}@media (width>=1280px){.lg\:bui-position-absolute{position:absolute}.lg\:bui-position-fixed{position:fixed}.lg\:bui-position-sticky{position:sticky}.lg\:bui-position-relative{position:relative}.lg\:bui-position-static{position:static}}@media (width>=1536px){.xl\:bui-position-absolute{position:absolute}.xl\:bui-position-fixed{position:fixed}.xl\:bui-position-sticky{position:sticky}.xl\:bui-position-relative{position:relative}.xl\:bui-position-static{position:static}}.bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.bui-col-span-1{grid-column:span 1/span 1}.bui-col-span-2{grid-column:span 2/span 2}.bui-col-span-3{grid-column:span 3/span 3}.bui-col-span-4{grid-column:span 4/span 4}.bui-col-span-5{grid-column:span 5/span 5}.bui-col-span-6{grid-column:span 6/span 6}.bui-col-span-7{grid-column:span 7/span 7}.bui-col-span-8{grid-column:span 8/span 8}.bui-col-span-9{grid-column:span 9/span 9}.bui-col-span-10{grid-column:span 10/span 10}.bui-col-span-11{grid-column:span 11/span 11}.bui-col-span-12{grid-column:span 12/span 12}.bui-col-span-auto{grid-column:span auto/span auto}.bui-col-start-1{grid-column-start:1}.bui-col-start-2{grid-column-start:2}.bui-col-start-3{grid-column-start:3}.bui-col-start-4{grid-column-start:4}.bui-col-start-5{grid-column-start:5}.bui-col-start-6{grid-column-start:6}.bui-col-start-7{grid-column-start:7}.bui-col-start-8{grid-column-start:8}.bui-col-start-9{grid-column-start:9}.bui-col-start-10{grid-column-start:10}.bui-col-start-11{grid-column-start:11}.bui-col-start-12{grid-column-start:12}.bui-col-start-13{grid-column-start:13}.bui-col-start-auto{grid-column-start:auto}.bui-col-end-1{grid-column-end:1}.bui-col-end-2{grid-column-end:2}.bui-col-end-3{grid-column-end:3}.bui-col-end-4{grid-column-end:4}.bui-col-end-5{grid-column-end:5}.bui-col-end-6{grid-column-end:6}.bui-col-end-7{grid-column-end:7}.bui-col-end-8{grid-column-end:8}.bui-col-end-9{grid-column-end:9}.bui-col-end-10{grid-column-end:10}.bui-col-end-11{grid-column-end:11}.bui-col-end-12{grid-column-end:12}.bui-col-end-13{grid-column-end:13}.bui-col-end-auto{grid-column-end:auto}.bui-row-span-1{grid-row:span 1/span 1}.bui-row-span-2{grid-row:span 2/span 2}.bui-row-span-3{grid-row:span 3/span 3}.bui-row-span-4{grid-row:span 4/span 4}.bui-row-span-5{grid-row:span 5/span 5}.bui-row-span-6{grid-row:span 6/span 6}.bui-row-span-7{grid-row:span 7/span 7}.bui-row-span-8{grid-row:span 8/span 8}.bui-row-span-9{grid-row:span 9/span 9}.bui-row-span-10{grid-row:span 10/span 10}.bui-row-span-11{grid-row:span 11/span 11}.bui-row-span-12{grid-row:span 12/span 12}.bui-row-span-auto{grid-row:span auto/span auto}@media (width>=640px){.xs\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xs\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xs\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xs\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xs\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xs\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xs\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.xs\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.xs\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.xs\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xs\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.xs\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xs\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.xs\:bui-col-span-1{grid-column:span 1/span 1}.xs\:bui-col-span-2{grid-column:span 2/span 2}.xs\:bui-col-span-3{grid-column:span 3/span 3}.xs\:bui-col-span-4{grid-column:span 4/span 4}.xs\:bui-col-span-5{grid-column:span 5/span 5}.xs\:bui-col-span-6{grid-column:span 6/span 6}.xs\:bui-col-span-7{grid-column:span 7/span 7}.xs\:bui-col-span-8{grid-column:span 8/span 8}.xs\:bui-col-span-9{grid-column:span 9/span 9}.xs\:bui-col-span-10{grid-column:span 10/span 10}.xs\:bui-col-span-11{grid-column:span 11/span 11}.xs\:bui-col-span-12{grid-column:span 12/span 12}.xs\:bui-col-span-auto{grid-column:span auto/span auto}.xs\:bui-col-start-1{grid-column-start:1}.xs\:bui-col-start-2{grid-column-start:2}.xs\:bui-col-start-3{grid-column-start:3}.xs\:bui-col-start-4{grid-column-start:4}.xs\:bui-col-start-5{grid-column-start:5}.xs\:bui-col-start-6{grid-column-start:6}.xs\:bui-col-start-7{grid-column-start:7}.xs\:bui-col-start-8{grid-column-start:8}.xs\:bui-col-start-9{grid-column-start:9}.xs\:bui-col-start-10{grid-column-start:10}.xs\:bui-col-start-11{grid-column-start:11}.xs\:bui-col-start-12{grid-column-start:12}.xs\:bui-col-start-13{grid-column-start:13}.xs\:bui-col-start-auto{grid-column-start:auto}.xs\:bui-col-end-1{grid-column-end:1}.xs\:bui-col-end-2{grid-column-end:2}.xs\:bui-col-end-3{grid-column-end:3}.xs\:bui-col-end-4{grid-column-end:4}.xs\:bui-col-end-5{grid-column-end:5}.xs\:bui-col-end-6{grid-column-end:6}.xs\:bui-col-end-7{grid-column-end:7}.xs\:bui-col-end-8{grid-column-end:8}.xs\:bui-col-end-9{grid-column-end:9}.xs\:bui-col-end-10{grid-column-end:10}.xs\:bui-col-end-11{grid-column-end:11}.xs\:bui-col-end-12{grid-column-end:12}.xs\:bui-col-end-13{grid-column-end:13}.xs\:bui-col-end-auto{grid-column-end:auto}.xs\:bui-row-span-1{grid-row:span 1/span 1}.xs\:bui-row-span-2{grid-row:span 2/span 2}.xs\:bui-row-span-3{grid-row:span 3/span 3}.xs\:bui-row-span-4{grid-row:span 4/span 4}.xs\:bui-row-span-5{grid-row:span 5/span 5}.xs\:bui-row-span-6{grid-row:span 6/span 6}.xs\:bui-row-span-7{grid-row:span 7/span 7}.xs\:bui-row-span-8{grid-row:span 8/span 8}.xs\:bui-row-span-9{grid-row:span 9/span 9}.xs\:bui-row-span-10{grid-row:span 10/span 10}.xs\:bui-row-span-11{grid-row:span 11/span 11}.xs\:bui-row-span-12{grid-row:span 12/span 12}.xs\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=768px){.sm\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.sm\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.sm\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.sm\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.sm\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.sm\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.sm\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.sm\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.sm\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.sm\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.sm\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.sm\:bui-col-span-1{grid-column:span 1/span 1}.sm\:bui-col-span-2{grid-column:span 2/span 2}.sm\:bui-col-span-3{grid-column:span 3/span 3}.sm\:bui-col-span-4{grid-column:span 4/span 4}.sm\:bui-col-span-5{grid-column:span 5/span 5}.sm\:bui-col-span-6{grid-column:span 6/span 6}.sm\:bui-col-span-7{grid-column:span 7/span 7}.sm\:bui-col-span-8{grid-column:span 8/span 8}.sm\:bui-col-span-9{grid-column:span 9/span 9}.sm\:bui-col-span-10{grid-column:span 10/span 10}.sm\:bui-col-span-11{grid-column:span 11/span 11}.sm\:bui-col-span-12{grid-column:span 12/span 12}.sm\:bui-col-span-auto{grid-column:span auto/span auto}.sm\:bui-col-start-1{grid-column-start:1}.sm\:bui-col-start-2{grid-column-start:2}.sm\:bui-col-start-3{grid-column-start:3}.sm\:bui-col-start-4{grid-column-start:4}.sm\:bui-col-start-5{grid-column-start:5}.sm\:bui-col-start-6{grid-column-start:6}.sm\:bui-col-start-7{grid-column-start:7}.sm\:bui-col-start-8{grid-column-start:8}.sm\:bui-col-start-9{grid-column-start:9}.sm\:bui-col-start-10{grid-column-start:10}.sm\:bui-col-start-11{grid-column-start:11}.sm\:bui-col-start-12{grid-column-start:12}.sm\:bui-col-start-13{grid-column-start:13}.sm\:bui-col-start-auto{grid-column-start:auto}.sm\:bui-col-end-1{grid-column-end:1}.sm\:bui-col-end-2{grid-column-end:2}.sm\:bui-col-end-3{grid-column-end:3}.sm\:bui-col-end-4{grid-column-end:4}.sm\:bui-col-end-5{grid-column-end:5}.sm\:bui-col-end-6{grid-column-end:6}.sm\:bui-col-end-7{grid-column-end:7}.sm\:bui-col-end-8{grid-column-end:8}.sm\:bui-col-end-9{grid-column-end:9}.sm\:bui-col-end-10{grid-column-end:10}.sm\:bui-col-end-11{grid-column-end:11}.sm\:bui-col-end-12{grid-column-end:12}.sm\:bui-col-end-13{grid-column-end:13}.sm\:bui-col-end-auto{grid-column-end:auto}.sm\:bui-row-span-1{grid-row:span 1/span 1}.sm\:bui-row-span-2{grid-row:span 2/span 2}.sm\:bui-row-span-3{grid-row:span 3/span 3}.sm\:bui-row-span-4{grid-row:span 4/span 4}.sm\:bui-row-span-5{grid-row:span 5/span 5}.sm\:bui-row-span-6{grid-row:span 6/span 6}.sm\:bui-row-span-7{grid-row:span 7/span 7}.sm\:bui-row-span-8{grid-row:span 8/span 8}.sm\:bui-row-span-9{grid-row:span 9/span 9}.sm\:bui-row-span-10{grid-row:span 10/span 10}.sm\:bui-row-span-11{grid-row:span 11/span 11}.sm\:bui-row-span-12{grid-row:span 12/span 12}.sm\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1024px){.md\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.md\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.md\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.md\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.md\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.md\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.md\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.md\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.md\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.md\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.md\:bui-col-span-1{grid-column:span 1/span 1}.md\:bui-col-span-2{grid-column:span 2/span 2}.md\:bui-col-span-3{grid-column:span 3/span 3}.md\:bui-col-span-4{grid-column:span 4/span 4}.md\:bui-col-span-5{grid-column:span 5/span 5}.md\:bui-col-span-6{grid-column:span 6/span 6}.md\:bui-col-span-7{grid-column:span 7/span 7}.md\:bui-col-span-8{grid-column:span 8/span 8}.md\:bui-col-span-9{grid-column:span 9/span 9}.md\:bui-col-span-10{grid-column:span 10/span 10}.md\:bui-col-span-11{grid-column:span 11/span 11}.md\:bui-col-span-12{grid-column:span 12/span 12}.md\:bui-col-span-auto{grid-column:span auto/span auto}.md\:bui-col-start-1{grid-column-start:1}.md\:bui-col-start-2{grid-column-start:2}.md\:bui-col-start-3{grid-column-start:3}.md\:bui-col-start-4{grid-column-start:4}.md\:bui-col-start-5{grid-column-start:5}.md\:bui-col-start-6{grid-column-start:6}.md\:bui-col-start-7{grid-column-start:7}.md\:bui-col-start-8{grid-column-start:8}.md\:bui-col-start-9{grid-column-start:9}.md\:bui-col-start-10{grid-column-start:10}.md\:bui-col-start-11{grid-column-start:11}.md\:bui-col-start-12{grid-column-start:12}.md\:bui-col-start-13{grid-column-start:13}.md\:bui-col-start-auto{grid-column-start:auto}.md\:bui-col-end-1{grid-column-end:1}.md\:bui-col-end-2{grid-column-end:2}.md\:bui-col-end-3{grid-column-end:3}.md\:bui-col-end-4{grid-column-end:4}.md\:bui-col-end-5{grid-column-end:5}.md\:bui-col-end-6{grid-column-end:6}.md\:bui-col-end-7{grid-column-end:7}.md\:bui-col-end-8{grid-column-end:8}.md\:bui-col-end-9{grid-column-end:9}.md\:bui-col-end-10{grid-column-end:10}.md\:bui-col-end-11{grid-column-end:11}.md\:bui-col-end-12{grid-column-end:12}.md\:bui-col-end-13{grid-column-end:13}.md\:bui-col-end-auto{grid-column-end:auto}.md\:bui-row-span-1{grid-row:span 1/span 1}.md\:bui-row-span-2{grid-row:span 2/span 2}.md\:bui-row-span-3{grid-row:span 3/span 3}.md\:bui-row-span-4{grid-row:span 4/span 4}.md\:bui-row-span-5{grid-row:span 5/span 5}.md\:bui-row-span-6{grid-row:span 6/span 6}.md\:bui-row-span-7{grid-row:span 7/span 7}.md\:bui-row-span-8{grid-row:span 8/span 8}.md\:bui-row-span-9{grid-row:span 9/span 9}.md\:bui-row-span-10{grid-row:span 10/span 10}.md\:bui-row-span-11{grid-row:span 11/span 11}.md\:bui-row-span-12{grid-row:span 12/span 12}.md\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1280px){.lg\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.lg\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.lg\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.lg\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.lg\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.lg\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.lg\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.lg\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.lg\:bui-col-span-1{grid-column:span 1/span 1}.lg\:bui-col-span-2{grid-column:span 2/span 2}.lg\:bui-col-span-3{grid-column:span 3/span 3}.lg\:bui-col-span-4{grid-column:span 4/span 4}.lg\:bui-col-span-5{grid-column:span 5/span 5}.lg\:bui-col-span-6{grid-column:span 6/span 6}.lg\:bui-col-span-7{grid-column:span 7/span 7}.lg\:bui-col-span-8{grid-column:span 8/span 8}.lg\:bui-col-span-9{grid-column:span 9/span 9}.lg\:bui-col-span-10{grid-column:span 10/span 10}.lg\:bui-col-span-11{grid-column:span 11/span 11}.lg\:bui-col-span-12{grid-column:span 12/span 12}.lg\:bui-col-span-auto{grid-column:span auto/span auto}.lg\:bui-col-start-1{grid-column-start:1}.lg\:bui-col-start-2{grid-column-start:2}.lg\:bui-col-start-3{grid-column-start:3}.lg\:bui-col-start-4{grid-column-start:4}.lg\:bui-col-start-5{grid-column-start:5}.lg\:bui-col-start-6{grid-column-start:6}.lg\:bui-col-start-7{grid-column-start:7}.lg\:bui-col-start-8{grid-column-start:8}.lg\:bui-col-start-9{grid-column-start:9}.lg\:bui-col-start-10{grid-column-start:10}.lg\:bui-col-start-11{grid-column-start:11}.lg\:bui-col-start-12{grid-column-start:12}.lg\:bui-col-start-13{grid-column-start:13}.lg\:bui-col-start-auto{grid-column-start:auto}.lg\:bui-col-end-1{grid-column-end:1}.lg\:bui-col-end-2{grid-column-end:2}.lg\:bui-col-end-3{grid-column-end:3}.lg\:bui-col-end-4{grid-column-end:4}.lg\:bui-col-end-5{grid-column-end:5}.lg\:bui-col-end-6{grid-column-end:6}.lg\:bui-col-end-7{grid-column-end:7}.lg\:bui-col-end-8{grid-column-end:8}.lg\:bui-col-end-9{grid-column-end:9}.lg\:bui-col-end-10{grid-column-end:10}.lg\:bui-col-end-11{grid-column-end:11}.lg\:bui-col-end-12{grid-column-end:12}.lg\:bui-col-end-13{grid-column-end:13}.lg\:bui-col-end-auto{grid-column-end:auto}.lg\:bui-row-span-1{grid-row:span 1/span 1}.lg\:bui-row-span-2{grid-row:span 2/span 2}.lg\:bui-row-span-3{grid-row:span 3/span 3}.lg\:bui-row-span-4{grid-row:span 4/span 4}.lg\:bui-row-span-5{grid-row:span 5/span 5}.lg\:bui-row-span-6{grid-row:span 6/span 6}.lg\:bui-row-span-7{grid-row:span 7/span 7}.lg\:bui-row-span-8{grid-row:span 8/span 8}.lg\:bui-row-span-9{grid-row:span 9/span 9}.lg\:bui-row-span-10{grid-row:span 10/span 10}.lg\:bui-row-span-11{grid-row:span 11/span 11}.lg\:bui-row-span-12{grid-row:span 12/span 12}.lg\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1536px){.xl\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xl\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xl\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xl\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xl\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xl\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xl\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.xl\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.xl\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.xl\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.xl\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xl\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.xl\:bui-col-span-1{grid-column:span 1/span 1}.xl\:bui-col-span-2{grid-column:span 2/span 2}.xl\:bui-col-span-3{grid-column:span 3/span 3}.xl\:bui-col-span-4{grid-column:span 4/span 4}.xl\:bui-col-span-5{grid-column:span 5/span 5}.xl\:bui-col-span-6{grid-column:span 6/span 6}.xl\:bui-col-span-7{grid-column:span 7/span 7}.xl\:bui-col-span-8{grid-column:span 8/span 8}.xl\:bui-col-span-9{grid-column:span 9/span 9}.xl\:bui-col-span-10{grid-column:span 10/span 10}.xl\:bui-col-span-11{grid-column:span 11/span 11}.xl\:bui-col-span-12{grid-column:span 12/span 12}.xl\:bui-col-span-auto{grid-column:span auto/span auto}.xl\:bui-col-start-1{grid-column-start:1}.xl\:bui-col-start-2{grid-column-start:2}.xl\:bui-col-start-3{grid-column-start:3}.xl\:bui-col-start-4{grid-column-start:4}.xl\:bui-col-start-5{grid-column-start:5}.xl\:bui-col-start-6{grid-column-start:6}.xl\:bui-col-start-7{grid-column-start:7}.xl\:bui-col-start-8{grid-column-start:8}.xl\:bui-col-start-9{grid-column-start:9}.xl\:bui-col-start-10{grid-column-start:10}.xl\:bui-col-start-11{grid-column-start:11}.xl\:bui-col-start-12{grid-column-start:12}.xl\:bui-col-start-13{grid-column-start:13}.xl\:bui-col-start-auto{grid-column-start:auto}.xl\:bui-col-end-1{grid-column-end:1}.xl\:bui-col-end-2{grid-column-end:2}.xl\:bui-col-end-3{grid-column-end:3}.xl\:bui-col-end-4{grid-column-end:4}.xl\:bui-col-end-5{grid-column-end:5}.xl\:bui-col-end-6{grid-column-end:6}.xl\:bui-col-end-7{grid-column-end:7}.xl\:bui-col-end-8{grid-column-end:8}.xl\:bui-col-end-9{grid-column-end:9}.xl\:bui-col-end-10{grid-column-end:10}.xl\:bui-col-end-11{grid-column-end:11}.xl\:bui-col-end-12{grid-column-end:12}.xl\:bui-col-end-13{grid-column-end:13}.xl\:bui-col-end-auto{grid-column-end:auto}.xl\:bui-row-span-1{grid-row:span 1/span 1}.xl\:bui-row-span-2{grid-row:span 2/span 2}.xl\:bui-row-span-3{grid-row:span 3/span 3}.xl\:bui-row-span-4{grid-row:span 4/span 4}.xl\:bui-row-span-5{grid-row:span 5/span 5}.xl\:bui-row-span-6{grid-row:span 6/span 6}.xl\:bui-row-span-7{grid-row:span 7/span 7}.xl\:bui-row-span-8{grid-row:span 8/span 8}.xl\:bui-row-span-9{grid-row:span 9/span 9}.xl\:bui-row-span-10{grid-row:span 10/span 10}.xl\:bui-row-span-11{grid-row:span 11/span 11}.xl\:bui-row-span-12{grid-row:span 12/span 12}.xl\:bui-row-span-auto{grid-row:span auto/span auto}}.bui-gap{gap:var(--gap)}.bui-gap-0\.5{gap:var(--bui-space-0_5)}.bui-gap-1{gap:var(--bui-space-1)}.bui-gap-1\.5{gap:var(--bui-space-1_5)}.bui-gap-2{gap:var(--bui-space-2)}.bui-gap-3{gap:var(--bui-space-3)}.bui-gap-4{gap:var(--bui-space-4)}.bui-gap-5{gap:var(--bui-space-5)}.bui-gap-6{gap:var(--bui-space-6)}.bui-gap-7{gap:var(--bui-space-7)}.bui-gap-8{gap:var(--bui-space-8)}.bui-gap-9{gap:var(--bui-space-9)}.bui-gap-10{gap:var(--bui-space-10)}.bui-gap-11{gap:var(--bui-space-11)}.bui-gap-12{gap:var(--bui-space-12)}.bui-gap-13{gap:var(--bui-space-13)}.bui-gap-14{gap:var(--bui-space-14)}@media (width>=640px){.xs\:bui-gap{gap:var(--gap-xs)}.xs\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.xs\:bui-gap-1{gap:var(--bui-space-1)}.xs\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.xs\:bui-gap-2{gap:var(--bui-space-2)}.xs\:bui-gap-3{gap:var(--bui-space-3)}.xs\:bui-gap-4{gap:var(--bui-space-4)}.xs\:bui-gap-5{gap:var(--bui-space-5)}.xs\:bui-gap-6{gap:var(--bui-space-6)}.xs\:bui-gap-7{gap:var(--bui-space-7)}.xs\:bui-gap-8{gap:var(--bui-space-8)}.xs\:bui-gap-9{gap:var(--bui-space-9)}.xs\:bui-gap-10{gap:var(--bui-space-10)}.xs\:bui-gap-11{gap:var(--bui-space-11)}.xs\:bui-gap-12{gap:var(--bui-space-12)}.xs\:bui-gap-13{gap:var(--bui-space-13)}.xs\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-gap{gap:var(--gap-sm)}.sm\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.sm\:bui-gap-1{gap:var(--bui-space-1)}.sm\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.sm\:bui-gap-2{gap:var(--bui-space-2)}.sm\:bui-gap-3{gap:var(--bui-space-3)}.sm\:bui-gap-4{gap:var(--bui-space-4)}.sm\:bui-gap-5{gap:var(--bui-space-5)}.sm\:bui-gap-6{gap:var(--bui-space-6)}.sm\:bui-gap-7{gap:var(--bui-space-7)}.sm\:bui-gap-8{gap:var(--bui-space-8)}.sm\:bui-gap-9{gap:var(--bui-space-9)}.sm\:bui-gap-10{gap:var(--bui-space-10)}.sm\:bui-gap-11{gap:var(--bui-space-11)}.sm\:bui-gap-12{gap:var(--bui-space-12)}.sm\:bui-gap-13{gap:var(--bui-space-13)}.sm\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-gap{gap:var(--gap-md)}.md\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.md\:bui-gap-1{gap:var(--bui-space-1)}.md\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.md\:bui-gap-2{gap:var(--bui-space-2)}.md\:bui-gap-3{gap:var(--bui-space-3)}.md\:bui-gap-4{gap:var(--bui-space-4)}.md\:bui-gap-5{gap:var(--bui-space-5)}.md\:bui-gap-6{gap:var(--bui-space-6)}.md\:bui-gap-7{gap:var(--bui-space-7)}.md\:bui-gap-8{gap:var(--bui-space-8)}.md\:bui-gap-9{gap:var(--bui-space-9)}.md\:bui-gap-10{gap:var(--bui-space-10)}.md\:bui-gap-11{gap:var(--bui-space-11)}.md\:bui-gap-12{gap:var(--bui-space-12)}.md\:bui-gap-13{gap:var(--bui-space-13)}.md\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-gap{gap:var(--gap-lg)}.lg\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.lg\:bui-gap-1{gap:var(--bui-space-1)}.lg\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.lg\:bui-gap-2{gap:var(--bui-space-2)}.lg\:bui-gap-3{gap:var(--bui-space-3)}.lg\:bui-gap-4{gap:var(--bui-space-4)}.lg\:bui-gap-5{gap:var(--bui-space-5)}.lg\:bui-gap-6{gap:var(--bui-space-6)}.lg\:bui-gap-7{gap:var(--bui-space-7)}.lg\:bui-gap-8{gap:var(--bui-space-8)}.lg\:bui-gap-9{gap:var(--bui-space-9)}.lg\:bui-gap-10{gap:var(--bui-space-10)}.lg\:bui-gap-11{gap:var(--bui-space-11)}.lg\:bui-gap-12{gap:var(--bui-space-12)}.lg\:bui-gap-13{gap:var(--bui-space-13)}.lg\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-gap{gap:var(--gap-xl)}.xl\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.xl\:bui-gap-1{gap:var(--bui-space-1)}.xl\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.xl\:bui-gap-2{gap:var(--bui-space-2)}.xl\:bui-gap-3{gap:var(--bui-space-3)}.xl\:bui-gap-4{gap:var(--bui-space-4)}.xl\:bui-gap-5{gap:var(--bui-space-5)}.xl\:bui-gap-6{gap:var(--bui-space-6)}.xl\:bui-gap-7{gap:var(--bui-space-7)}.xl\:bui-gap-8{gap:var(--bui-space-8)}.xl\:bui-gap-9{gap:var(--bui-space-9)}.xl\:bui-gap-10{gap:var(--bui-space-10)}.xl\:bui-gap-11{gap:var(--bui-space-11)}.xl\:bui-gap-12{gap:var(--bui-space-12)}.xl\:bui-gap-13{gap:var(--bui-space-13)}.xl\:bui-gap-14{gap:var(--bui-space-14)}}.bui-align-start{align-items:start}.bui-align-center{align-items:center}.bui-align-end{align-items:end}.bui-align-baseline{align-items:baseline}.bui-align-stretch{align-items:stretch}.bui-jc-start{justify-content:start}.bui-jc-center{justify-content:center}.bui-jc-end{justify-content:end}.bui-jc-between{justify-content:space-between}.bui-fd-row{flex-direction:row}.bui-fd-column{flex-direction:column}.bui-fd-row-reverse{flex-direction:row-reverse}.bui-fd-column-reverse{flex-direction:column-reverse}@media (width>=640px){.xs\:bui-align-start{align-items:start}.xs\:bui-align-center{align-items:center}.xs\:bui-align-end{align-items:end}.xs\:bui-align-stretch{align-items:stretch}.xs\:bui-jc-start{justify-content:start}.xs\:bui-jc-center{justify-content:center}.xs\:bui-jc-end{justify-content:end}.xs\:bui-jc-between{justify-content:space-between}.xs\:bui-fd-row{flex-direction:row}.xs\:bui-fd-column{flex-direction:column}.xs\:bui-fd-row-reverse{flex-direction:row-reverse}.xs\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=768px){.sm\:bui-align-start{align-items:start}.sm\:bui-align-center{align-items:center}.sm\:bui-align-end{align-items:end}.sm\:bui-align-stretch{align-items:stretch}.sm\:bui-jc-start{justify-content:start}.sm\:bui-jc-center{justify-content:center}.sm\:bui-jc-end{justify-content:end}.sm\:bui-jc-between{justify-content:space-between}.sm\:bui-fd-row{flex-direction:row}.sm\:bui-fd-column{flex-direction:column}.sm\:bui-fd-row-reverse{flex-direction:row-reverse}.sm\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1024px){.md\:bui-align-start{align-items:start}.md\:bui-align-center{align-items:center}.md\:bui-align-end{align-items:end}.md\:bui-align-stretch{align-items:stretch}.md\:bui-jc-start{justify-content:start}.md\:bui-jc-center{justify-content:center}.md\:bui-jc-end{justify-content:end}.md\:bui-jc-between{justify-content:space-between}.md\:bui-fd-row{flex-direction:row}.md\:bui-fd-column{flex-direction:column}.md\:bui-fd-row-reverse{flex-direction:row-reverse}.md\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1280px){.lg\:bui-align-start{align-items:start}.lg\:bui-align-center{align-items:center}.lg\:bui-align-end{align-items:end}.lg\:bui-align-stretch{align-items:stretch}.lg\:bui-jc-start{justify-content:start}.lg\:bui-jc-center{justify-content:center}.lg\:bui-jc-end{justify-content:end}.lg\:bui-jc-between{justify-content:space-between}.lg\:bui-fd-row{flex-direction:row}.lg\:bui-fd-column{flex-direction:column}.lg\:bui-fd-row-reverse{flex-direction:row-reverse}.lg\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1536px){.xl\:bui-align-start{align-items:start}.xl\:bui-align-center{align-items:center}.xl\:bui-align-end{align-items:end}.xl\:bui-align-stretch{align-items:stretch}.xl\:bui-jc-start{justify-content:start}.xl\:bui-jc-center{justify-content:center}.xl\:bui-jc-end{justify-content:end}.xl\:bui-jc-between{justify-content:space-between}.xl\:bui-fd-row{flex-direction:row}.xl\:bui-fd-column{flex-direction:column}.xl\:bui-fd-row-reverse{flex-direction:row-reverse}.xl\:bui-fd-column-reverse{flex-direction:column-reverse}}} \ No newline at end of file +@layer tokens{:root{--bui-font-regular:system-ui;--bui-font-monospace:ui-monospace,"Menlo","Monaco","Consolas","Liberation Mono","Courier New",monospace;--bui-font-weight-regular:400;--bui-font-weight-bold:600;--bui-font-size-1:.625rem;--bui-font-size-2:.75rem;--bui-font-size-3:.875rem;--bui-font-size-4:1rem;--bui-font-size-5:1.25rem;--bui-font-size-6:1.5rem;--bui-font-size-7:2rem;--bui-font-size-8:3rem;--bui-font-size-9:4rem;--bui-font-size-10:5.75rem;--bui-space:.25rem;--bui-space-0_5:calc(var(--bui-space)*.5);--bui-space-1:var(--bui-space);--bui-space-1_5:calc(var(--bui-space)*1.5);--bui-space-2:calc(var(--bui-space)*2);--bui-space-3:calc(var(--bui-space)*3);--bui-space-4:calc(var(--bui-space)*4);--bui-space-5:calc(var(--bui-space)*5);--bui-space-6:calc(var(--bui-space)*6);--bui-space-7:calc(var(--bui-space)*7);--bui-space-8:calc(var(--bui-space)*8);--bui-space-9:calc(var(--bui-space)*9);--bui-space-10:calc(var(--bui-space)*10);--bui-space-11:calc(var(--bui-space)*11);--bui-space-12:calc(var(--bui-space)*12);--bui-space-13:calc(var(--bui-space)*13);--bui-space-14:calc(var(--bui-space)*14);--bui-radius-1:calc(.125rem);--bui-radius-2:calc(.25rem);--bui-radius-3:calc(.5rem);--bui-radius-4:calc(.75rem);--bui-radius-5:calc(1rem);--bui-radius-6:calc(1.25rem);--bui-radius-full:9999px;--bui-black:#000;--bui-white:#fff;--bui-gray-1:#f8f8f8;--bui-gray-2:#ececec;--bui-gray-3:#d9d9d9;--bui-gray-4:#c1c1c1;--bui-gray-5:#9e9e9e;--bui-gray-6:#8c8c8c;--bui-gray-7:#757575;--bui-gray-8:#595959;--bui-bg-surface-0:var(--bui-gray-1);--bui-bg-surface-1:var(--bui-white);--bui-bg-surface-2:var(--bui-gray-1);--bui-bg-surface-3:var(--bui-gray-2);--bui-bg-solid:#1f5493;--bui-bg-solid-hover:#163a66;--bui-bg-solid-pressed:#0f2b4e;--bui-bg-solid-disabled:#ebebeb;--bui-bg-tint:transparent;--bui-bg-tint-hover:#1f549366;--bui-bg-tint-pressed:#1f549399;--bui-bg-tint-disabled:#ebebeb;--bui-bg-danger:#feebe7;--bui-bg-warning:#fff2b2;--bui-bg-success:#e6f6eb;--bui-fg-primary:var(--bui-black);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:#1f5493;--bui-fg-link-hover:#1f2d5c;--bui-fg-disabled:#9e9e9e;--bui-fg-solid:var(--bui-white);--bui-fg-solid-disabled:#9c9c9c;--bui-fg-tint:#1f5493;--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#e22b2b;--bui-fg-warning:#e36d05;--bui-fg-success:#1db954;--bui-border:#0000001a;--bui-border-hover:#0003;--bui-border-pressed:#0006;--bui-border-disabled:#0000001a;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#1f5493;--bui-scrollbar:#a0a0a03b;--bui-scrollbar-thumb:#a0a0a0;--bui-animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite}[data-theme-mode=dark]{--bui-gray-1:#191919;--bui-gray-2:#242424;--bui-gray-3:#373737;--bui-gray-4:#464646;--bui-gray-5:#575757;--bui-gray-6:#7b7b7b;--bui-gray-7:#9e9e9e;--bui-gray-8:#b4b4b4;--bui-bg-surface-0:#333;--bui-bg-surface-1:var(--bui-gray-1);--bui-bg-surface-2:var(--bui-gray-2);--bui-bg-surface-3:var(--bui-gray-3);--bui-bg-solid:#9cc9ff;--bui-bg-solid-hover:#83b9fd;--bui-bg-solid-pressed:#83b9fd;--bui-bg-solid-disabled:#222;--bui-bg-tint:transparent;--bui-bg-tint-hover:#9cc9ff1f;--bui-bg-tint-pressed:#9cc9ff29;--bui-bg-tint-disabled:transparent;--bui-bg-danger:#3b1219;--bui-bg-warning:#302008;--bui-bg-success:#132d21;--bui-fg-primary:var(--bui-white);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:#9cc9ff;--bui-fg-link-hover:#7eb5f7;--bui-fg-disabled:var(--bui-gray-7);--bui-fg-solid:#101821;--bui-fg-solid-disabled:var(--bui-gray-5);--bui-fg-tint:#9cc9ff;--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#e22b2b;--bui-fg-warning:#e36d05;--bui-fg-success:#1db954;--bui-border:#ffffff1f;--bui-border-hover:#fff6;--bui-border-pressed:#ffffff80;--bui-border-disabled:#fff3;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#1f5493;--bui-scrollbar:#3636363a;--bui-scrollbar-thumb:#575757}}@layer base{*,:before,:after{box-sizing:border-box}html{-webkit-text-size-adjust:100%;tab-size:4;font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;line-height:1.15}body{margin:0}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{border-color:currentColor}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:100%;line-height:1.15}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}legend{padding:0}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}:where(a){color:inherit;text-decoration:none}@keyframes pulse{50%{opacity:.5}}body{background-color:var(--bui-bg-surface-0);color:var(--bui-fg-primary);font-family:var(--bui-font-regular);font-weight:var(--bui-font-weight-regular);font-size:var(--bui-font-size-3);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}[data-theme-mode=dark]{color-scheme:dark}[data-theme-mode=light]{color-scheme:light}}@layer components;@layer utilities{.bui-p{padding:var(--p)}.bui-p-0\.5{padding:var(--bui-space-0_5)}.bui-p-1{padding:var(--bui-space-1)}.bui-p-1\.5{padding:var(--bui-space-1_5)}.bui-p-2{padding:var(--bui-space-2)}.bui-p-3{padding:var(--bui-space-3)}.bui-p-4{padding:var(--bui-space-4)}.bui-p-5{padding:var(--bui-space-5)}.bui-p-6{padding:var(--bui-space-6)}.bui-p-7{padding:var(--bui-space-7)}.bui-p-8{padding:var(--bui-space-8)}.bui-p-9{padding:var(--bui-space-9)}.bui-p-10{padding:var(--bui-space-10)}.bui-p-11{padding:var(--bui-space-11)}.bui-p-12{padding:var(--bui-space-12)}.bui-p-13{padding:var(--bui-space-13)}.bui-p-14{padding:var(--bui-space-14)}@media (width>=640px){.xs\:bui-p{padding:var(--p-xs)}.xs\:bui-p-0\.5{padding:var(--bui-space-0_5)}.xs\:bui-p-1{padding:var(--bui-space-1)}.xs\:bui-p-1\.5{padding:var(--bui-space-1_5)}.xs\:bui-p-2{padding:var(--bui-space-2)}.xs\:bui-p-3{padding:var(--bui-space-3)}.xs\:bui-p-4{padding:var(--bui-space-4)}.xs\:bui-p-5{padding:var(--bui-space-5)}.xs\:bui-p-6{padding:var(--bui-space-6)}.xs\:bui-p-7{padding:var(--bui-space-7)}.xs\:bui-p-8{padding:var(--bui-space-8)}.xs\:bui-p-9{padding:var(--bui-space-9)}.xs\:bui-p-10{padding:var(--bui-space-10)}.xs\:bui-p-11{padding:var(--bui-space-11)}.xs\:bui-p-12{padding:var(--bui-space-12)}.xs\:bui-p-13{padding:var(--bui-space-13)}.xs\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-p{padding:var(--p-sm)}.sm\:bui-p-0\.5{padding:var(--bui-space-0_5)}.sm\:bui-p-1{padding:var(--bui-space-1)}.sm\:bui-p-1\.5{padding:var(--bui-space-1_5)}.sm\:bui-p-2{padding:var(--bui-space-2)}.sm\:bui-p-3{padding:var(--bui-space-3)}.sm\:bui-p-4{padding:var(--bui-space-4)}.sm\:bui-p-5{padding:var(--bui-space-5)}.sm\:bui-p-6{padding:var(--bui-space-6)}.sm\:bui-p-7{padding:var(--bui-space-7)}.sm\:bui-p-8{padding:var(--bui-space-8)}.sm\:bui-p-9{padding:var(--bui-space-9)}.sm\:bui-p-10{padding:var(--bui-space-10)}.sm\:bui-p-11{padding:var(--bui-space-11)}.sm\:bui-p-12{padding:var(--bui-space-12)}.sm\:bui-p-13{padding:var(--bui-space-13)}.sm\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-p{padding:var(--p-md)}.md\:bui-p-0\.5{padding:var(--bui-space-0_5)}.md\:bui-p-1{padding:var(--bui-space-1)}.md\:bui-p-1\.5{padding:var(--bui-space-1_5)}.md\:bui-p-2{padding:var(--bui-space-2)}.md\:bui-p-3{padding:var(--bui-space-3)}.md\:bui-p-4{padding:var(--bui-space-4)}.md\:bui-p-5{padding:var(--bui-space-5)}.md\:bui-p-6{padding:var(--bui-space-6)}.md\:bui-p-7{padding:var(--bui-space-7)}.md\:bui-p-8{padding:var(--bui-space-8)}.md\:bui-p-9{padding:var(--bui-space-9)}.md\:bui-p-10{padding:var(--bui-space-10)}.md\:bui-p-11{padding:var(--bui-space-11)}.md\:bui-p-12{padding:var(--bui-space-12)}.md\:bui-p-13{padding:var(--bui-space-13)}.md\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-p{padding:var(--p-lg)}.lg\:bui-p-0\.5{padding:var(--bui-space-0_5)}.lg\:bui-p-1{padding:var(--bui-space-1)}.lg\:bui-p-1\.5{padding:var(--bui-space-1_5)}.lg\:bui-p-2{padding:var(--bui-space-2)}.lg\:bui-p-3{padding:var(--bui-space-3)}.lg\:bui-p-4{padding:var(--bui-space-4)}.lg\:bui-p-5{padding:var(--bui-space-5)}.lg\:bui-p-6{padding:var(--bui-space-6)}.lg\:bui-p-7{padding:var(--bui-space-7)}.lg\:bui-p-8{padding:var(--bui-space-8)}.lg\:bui-p-9{padding:var(--bui-space-9)}.lg\:bui-p-10{padding:var(--bui-space-10)}.lg\:bui-p-11{padding:var(--bui-space-11)}.lg\:bui-p-12{padding:var(--bui-space-12)}.lg\:bui-p-13{padding:var(--bui-space-13)}.lg\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-p{padding:var(--p-xl)}.xl\:bui-p-0\.5{padding:var(--bui-space-0_5)}.xl\:bui-p-1{padding:var(--bui-space-1)}.xl\:bui-p-1\.5{padding:var(--bui-space-1_5)}.xl\:bui-p-2{padding:var(--bui-space-2)}.xl\:bui-p-3{padding:var(--bui-space-3)}.xl\:bui-p-4{padding:var(--bui-space-4)}.xl\:bui-p-5{padding:var(--bui-space-5)}.xl\:bui-p-6{padding:var(--bui-space-6)}.xl\:bui-p-7{padding:var(--bui-space-7)}.xl\:bui-p-8{padding:var(--bui-space-8)}.xl\:bui-p-9{padding:var(--bui-space-9)}.xl\:bui-p-10{padding:var(--bui-space-10)}.xl\:bui-p-11{padding:var(--bui-space-11)}.xl\:bui-p-12{padding:var(--bui-space-12)}.xl\:bui-p-13{padding:var(--bui-space-13)}.xl\:bui-p-14{padding:var(--bui-space-14)}}.bui-pl{padding-left:var(--pl)}.bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.bui-pl-1{padding-left:var(--bui-space-1)}.bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.bui-pl-2{padding-left:var(--bui-space-2)}.bui-pl-3{padding-left:var(--bui-space-3)}.bui-pl-4{padding-left:var(--bui-space-4)}.bui-pl-5{padding-left:var(--bui-space-5)}.bui-pl-6{padding-left:var(--bui-space-6)}.bui-pl-7{padding-left:var(--bui-space-7)}.bui-pl-8{padding-left:var(--bui-space-8)}.bui-pl-9{padding-left:var(--bui-space-9)}.bui-pl-10{padding-left:var(--bui-space-10)}.bui-pl-11{padding-left:var(--bui-space-11)}.bui-pl-12{padding-left:var(--bui-space-12)}.bui-pl-13{padding-left:var(--bui-space-13)}.bui-pl-14{padding-left:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pl{padding-left:var(--pl-xs)}.xs\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.xs\:bui-pl-1{padding-left:var(--bui-space-1)}.xs\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.xs\:bui-pl-2{padding-left:var(--bui-space-2)}.xs\:bui-pl-3{padding-left:var(--bui-space-3)}.xs\:bui-pl-4{padding-left:var(--bui-space-4)}.xs\:bui-pl-5{padding-left:var(--bui-space-5)}.xs\:bui-pl-6{padding-left:var(--bui-space-6)}.xs\:bui-pl-7{padding-left:var(--bui-space-7)}.xs\:bui-pl-8{padding-left:var(--bui-space-8)}.xs\:bui-pl-9{padding-left:var(--bui-space-9)}.xs\:bui-pl-10{padding-left:var(--bui-space-10)}.xs\:bui-pl-11{padding-left:var(--bui-space-11)}.xs\:bui-pl-12{padding-left:var(--bui-space-12)}.xs\:bui-pl-13{padding-left:var(--bui-space-13)}.xs\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pl{padding-left:var(--pl-sm)}.sm\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.sm\:bui-pl-1{padding-left:var(--bui-space-1)}.sm\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.sm\:bui-pl-2{padding-left:var(--bui-space-2)}.sm\:bui-pl-3{padding-left:var(--bui-space-3)}.sm\:bui-pl-4{padding-left:var(--bui-space-4)}.sm\:bui-pl-5{padding-left:var(--bui-space-5)}.sm\:bui-pl-6{padding-left:var(--bui-space-6)}.sm\:bui-pl-7{padding-left:var(--bui-space-7)}.sm\:bui-pl-8{padding-left:var(--bui-space-8)}.sm\:bui-pl-9{padding-left:var(--bui-space-9)}.sm\:bui-pl-10{padding-left:var(--bui-space-10)}.sm\:bui-pl-11{padding-left:var(--bui-space-11)}.sm\:bui-pl-12{padding-left:var(--bui-space-12)}.sm\:bui-pl-13{padding-left:var(--bui-space-13)}.sm\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pl{padding-left:var(--pl-md)}.md\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.md\:bui-pl-1{padding-left:var(--bui-space-1)}.md\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.md\:bui-pl-2{padding-left:var(--bui-space-2)}.md\:bui-pl-3{padding-left:var(--bui-space-3)}.md\:bui-pl-4{padding-left:var(--bui-space-4)}.md\:bui-pl-5{padding-left:var(--bui-space-5)}.md\:bui-pl-6{padding-left:var(--bui-space-6)}.md\:bui-pl-7{padding-left:var(--bui-space-7)}.md\:bui-pl-8{padding-left:var(--bui-space-8)}.md\:bui-pl-9{padding-left:var(--bui-space-9)}.md\:bui-pl-10{padding-left:var(--bui-space-10)}.md\:bui-pl-11{padding-left:var(--bui-space-11)}.md\:bui-pl-12{padding-left:var(--bui-space-12)}.md\:bui-pl-13{padding-left:var(--bui-space-13)}.md\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pl{padding-left:var(--pl-lg)}.lg\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.lg\:bui-pl-1{padding-left:var(--bui-space-1)}.lg\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.lg\:bui-pl-2{padding-left:var(--bui-space-2)}.lg\:bui-pl-3{padding-left:var(--bui-space-3)}.lg\:bui-pl-4{padding-left:var(--bui-space-4)}.lg\:bui-pl-5{padding-left:var(--bui-space-5)}.lg\:bui-pl-6{padding-left:var(--bui-space-6)}.lg\:bui-pl-7{padding-left:var(--bui-space-7)}.lg\:bui-pl-8{padding-left:var(--bui-space-8)}.lg\:bui-pl-9{padding-left:var(--bui-space-9)}.lg\:bui-pl-10{padding-left:var(--bui-space-10)}.lg\:bui-pl-11{padding-left:var(--bui-space-11)}.lg\:bui-pl-12{padding-left:var(--bui-space-12)}.lg\:bui-pl-13{padding-left:var(--bui-space-13)}.lg\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pl{padding-left:var(--pl-xl)}.xl\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.xl\:bui-pl-1{padding-left:var(--bui-space-1)}.xl\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.xl\:bui-pl-2{padding-left:var(--bui-space-2)}.xl\:bui-pl-3{padding-left:var(--bui-space-3)}.xl\:bui-pl-4{padding-left:var(--bui-space-4)}.xl\:bui-pl-5{padding-left:var(--bui-space-5)}.xl\:bui-pl-6{padding-left:var(--bui-space-6)}.xl\:bui-pl-7{padding-left:var(--bui-space-7)}.xl\:bui-pl-8{padding-left:var(--bui-space-8)}.xl\:bui-pl-9{padding-left:var(--bui-space-9)}.xl\:bui-pl-10{padding-left:var(--bui-space-10)}.xl\:bui-pl-11{padding-left:var(--bui-space-11)}.xl\:bui-pl-12{padding-left:var(--bui-space-12)}.xl\:bui-pl-13{padding-left:var(--bui-space-13)}.xl\:bui-pl-14{padding-left:var(--bui-space-14)}}.bui-pr{padding-right:var(--pr)}.bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.bui-pr-1{padding-right:var(--bui-space-1)}.bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.bui-pr-2{padding-right:var(--bui-space-2)}.bui-pr-3{padding-right:var(--bui-space-3)}.bui-pr-4{padding-right:var(--bui-space-4)}.bui-pr-5{padding-right:var(--bui-space-5)}.bui-pr-6{padding-right:var(--bui-space-6)}.bui-pr-7{padding-right:var(--bui-space-7)}.bui-pr-8{padding-right:var(--bui-space-8)}.bui-pr-9{padding-right:var(--bui-space-9)}.bui-pr-10{padding-right:var(--bui-space-10)}.bui-pr-11{padding-right:var(--bui-space-11)}.bui-pr-12{padding-right:var(--bui-space-12)}.bui-pr-13{padding-right:var(--bui-space-13)}.bui-pr-14{padding-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pr{padding-right:var(--pr-xs)}.xs\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.xs\:bui-pr-1{padding-right:var(--bui-space-1)}.xs\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.xs\:bui-pr-2{padding-right:var(--bui-space-2)}.xs\:bui-pr-3{padding-right:var(--bui-space-3)}.xs\:bui-pr-4{padding-right:var(--bui-space-4)}.xs\:bui-pr-5{padding-right:var(--bui-space-5)}.xs\:bui-pr-6{padding-right:var(--bui-space-6)}.xs\:bui-pr-7{padding-right:var(--bui-space-7)}.xs\:bui-pr-8{padding-right:var(--bui-space-8)}.xs\:bui-pr-9{padding-right:var(--bui-space-9)}.xs\:bui-pr-10{padding-right:var(--bui-space-10)}.xs\:bui-pr-11{padding-right:var(--bui-space-11)}.xs\:bui-pr-12{padding-right:var(--bui-space-12)}.xs\:bui-pr-13{padding-right:var(--bui-space-13)}.xs\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pr{padding-right:var(--pr-sm)}.sm\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.sm\:bui-pr-1{padding-right:var(--bui-space-1)}.sm\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.sm\:bui-pr-2{padding-right:var(--bui-space-2)}.sm\:bui-pr-3{padding-right:var(--bui-space-3)}.sm\:bui-pr-4{padding-right:var(--bui-space-4)}.sm\:bui-pr-5{padding-right:var(--bui-space-5)}.sm\:bui-pr-6{padding-right:var(--bui-space-6)}.sm\:bui-pr-7{padding-right:var(--bui-space-7)}.sm\:bui-pr-8{padding-right:var(--bui-space-8)}.sm\:bui-pr-9{padding-right:var(--bui-space-9)}.sm\:bui-pr-10{padding-right:var(--bui-space-10)}.sm\:bui-pr-11{padding-right:var(--bui-space-11)}.sm\:bui-pr-12{padding-right:var(--bui-space-12)}.sm\:bui-pr-13{padding-right:var(--bui-space-13)}.sm\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pr{padding-right:var(--pr-md)}.md\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.md\:bui-pr-1{padding-right:var(--bui-space-1)}.md\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.md\:bui-pr-2{padding-right:var(--bui-space-2)}.md\:bui-pr-3{padding-right:var(--bui-space-3)}.md\:bui-pr-4{padding-right:var(--bui-space-4)}.md\:bui-pr-5{padding-right:var(--bui-space-5)}.md\:bui-pr-6{padding-right:var(--bui-space-6)}.md\:bui-pr-7{padding-right:var(--bui-space-7)}.md\:bui-pr-8{padding-right:var(--bui-space-8)}.md\:bui-pr-9{padding-right:var(--bui-space-9)}.md\:bui-pr-10{padding-right:var(--bui-space-10)}.md\:bui-pr-11{padding-right:var(--bui-space-11)}.md\:bui-pr-12{padding-right:var(--bui-space-12)}.md\:bui-pr-13{padding-right:var(--bui-space-13)}.md\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pr{padding-right:var(--pr-lg)}.lg\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.lg\:bui-pr-1{padding-right:var(--bui-space-1)}.lg\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.lg\:bui-pr-2{padding-right:var(--bui-space-2)}.lg\:bui-pr-3{padding-right:var(--bui-space-3)}.lg\:bui-pr-4{padding-right:var(--bui-space-4)}.lg\:bui-pr-5{padding-right:var(--bui-space-5)}.lg\:bui-pr-6{padding-right:var(--bui-space-6)}.lg\:bui-pr-7{padding-right:var(--bui-space-7)}.lg\:bui-pr-8{padding-right:var(--bui-space-8)}.lg\:bui-pr-9{padding-right:var(--bui-space-9)}.lg\:bui-pr-10{padding-right:var(--bui-space-10)}.lg\:bui-pr-11{padding-right:var(--bui-space-11)}.lg\:bui-pr-12{padding-right:var(--bui-space-12)}.lg\:bui-pr-13{padding-right:var(--bui-space-13)}.lg\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pr{padding-right:var(--pr-xl)}.xl\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.xl\:bui-pr-1{padding-right:var(--bui-space-1)}.xl\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.xl\:bui-pr-2{padding-right:var(--bui-space-2)}.xl\:bui-pr-3{padding-right:var(--bui-space-3)}.xl\:bui-pr-4{padding-right:var(--bui-space-4)}.xl\:bui-pr-5{padding-right:var(--bui-space-5)}.xl\:bui-pr-6{padding-right:var(--bui-space-6)}.xl\:bui-pr-7{padding-right:var(--bui-space-7)}.xl\:bui-pr-8{padding-right:var(--bui-space-8)}.xl\:bui-pr-9{padding-right:var(--bui-space-9)}.xl\:bui-pr-10{padding-right:var(--bui-space-10)}.xl\:bui-pr-11{padding-right:var(--bui-space-11)}.xl\:bui-pr-12{padding-right:var(--bui-space-12)}.xl\:bui-pr-13{padding-right:var(--bui-space-13)}.xl\:bui-pr-14{padding-right:var(--bui-space-14)}}.bui-pt{padding-top:var(--pt)}.bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.bui-pt-1{padding-top:var(--bui-space-1)}.bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.bui-pt-2{padding-top:var(--bui-space-2)}.bui-pt-3{padding-top:var(--bui-space-3)}.bui-pt-4{padding-top:var(--bui-space-4)}.bui-pt-5{padding-top:var(--bui-space-5)}.bui-pt-6{padding-top:var(--bui-space-6)}.bui-pt-7{padding-top:var(--bui-space-7)}.bui-pt-8{padding-top:var(--bui-space-8)}.bui-pt-9{padding-top:var(--bui-space-9)}.bui-pt-10{padding-top:var(--bui-space-10)}.bui-pt-11{padding-top:var(--bui-space-11)}.bui-pt-12{padding-top:var(--bui-space-12)}.bui-pt-13{padding-top:var(--bui-space-13)}.bui-pt-14{padding-top:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pt{padding-top:var(--pt-xs)}.xs\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.xs\:bui-pt-1{padding-top:var(--bui-space-1)}.xs\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.xs\:bui-pt-2{padding-top:var(--bui-space-2)}.xs\:bui-pt-3{padding-top:var(--bui-space-3)}.xs\:bui-pt-4{padding-top:var(--bui-space-4)}.xs\:bui-pt-5{padding-top:var(--bui-space-5)}.xs\:bui-pt-6{padding-top:var(--bui-space-6)}.xs\:bui-pt-7{padding-top:var(--bui-space-7)}.xs\:bui-pt-8{padding-top:var(--bui-space-8)}.xs\:bui-pt-9{padding-top:var(--bui-space-9)}.xs\:bui-pt-10{padding-top:var(--bui-space-10)}.xs\:bui-pt-11{padding-top:var(--bui-space-11)}.xs\:bui-pt-12{padding-top:var(--bui-space-12)}.xs\:bui-pt-13{padding-top:var(--bui-space-13)}.xs\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pt{padding-top:var(--pt-sm)}.sm\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.sm\:bui-pt-1{padding-top:var(--bui-space-1)}.sm\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.sm\:bui-pt-2{padding-top:var(--bui-space-2)}.sm\:bui-pt-3{padding-top:var(--bui-space-3)}.sm\:bui-pt-4{padding-top:var(--bui-space-4)}.sm\:bui-pt-5{padding-top:var(--bui-space-5)}.sm\:bui-pt-6{padding-top:var(--bui-space-6)}.sm\:bui-pt-7{padding-top:var(--bui-space-7)}.sm\:bui-pt-8{padding-top:var(--bui-space-8)}.sm\:bui-pt-9{padding-top:var(--bui-space-9)}.sm\:bui-pt-10{padding-top:var(--bui-space-10)}.sm\:bui-pt-11{padding-top:var(--bui-space-11)}.sm\:bui-pt-12{padding-top:var(--bui-space-12)}.sm\:bui-pt-13{padding-top:var(--bui-space-13)}.sm\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pt{padding-top:var(--pt-md)}.md\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.md\:bui-pt-1{padding-top:var(--bui-space-1)}.md\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.md\:bui-pt-2{padding-top:var(--bui-space-2)}.md\:bui-pt-3{padding-top:var(--bui-space-3)}.md\:bui-pt-4{padding-top:var(--bui-space-4)}.md\:bui-pt-5{padding-top:var(--bui-space-5)}.md\:bui-pt-6{padding-top:var(--bui-space-6)}.md\:bui-pt-7{padding-top:var(--bui-space-7)}.md\:bui-pt-8{padding-top:var(--bui-space-8)}.md\:bui-pt-9{padding-top:var(--bui-space-9)}.md\:bui-pt-10{padding-top:var(--bui-space-10)}.md\:bui-pt-11{padding-top:var(--bui-space-11)}.md\:bui-pt-12{padding-top:var(--bui-space-12)}.md\:bui-pt-13{padding-top:var(--bui-space-13)}.md\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pt{padding-top:var(--pt-lg)}.lg\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.lg\:bui-pt-1{padding-top:var(--bui-space-1)}.lg\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.lg\:bui-pt-2{padding-top:var(--bui-space-2)}.lg\:bui-pt-3{padding-top:var(--bui-space-3)}.lg\:bui-pt-4{padding-top:var(--bui-space-4)}.lg\:bui-pt-5{padding-top:var(--bui-space-5)}.lg\:bui-pt-6{padding-top:var(--bui-space-6)}.lg\:bui-pt-7{padding-top:var(--bui-space-7)}.lg\:bui-pt-8{padding-top:var(--bui-space-8)}.lg\:bui-pt-9{padding-top:var(--bui-space-9)}.lg\:bui-pt-10{padding-top:var(--bui-space-10)}.lg\:bui-pt-11{padding-top:var(--bui-space-11)}.lg\:bui-pt-12{padding-top:var(--bui-space-12)}.lg\:bui-pt-13{padding-top:var(--bui-space-13)}.lg\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pt{padding-top:var(--pt-xl)}.xl\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.xl\:bui-pt-1{padding-top:var(--bui-space-1)}.xl\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.xl\:bui-pt-2{padding-top:var(--bui-space-2)}.xl\:bui-pt-3{padding-top:var(--bui-space-3)}.xl\:bui-pt-4{padding-top:var(--bui-space-4)}.xl\:bui-pt-5{padding-top:var(--bui-space-5)}.xl\:bui-pt-6{padding-top:var(--bui-space-6)}.xl\:bui-pt-7{padding-top:var(--bui-space-7)}.xl\:bui-pt-8{padding-top:var(--bui-space-8)}.xl\:bui-pt-9{padding-top:var(--bui-space-9)}.xl\:bui-pt-10{padding-top:var(--bui-space-10)}.xl\:bui-pt-11{padding-top:var(--bui-space-11)}.xl\:bui-pt-12{padding-top:var(--bui-space-12)}.xl\:bui-pt-13{padding-top:var(--bui-space-13)}.xl\:bui-pt-14{padding-top:var(--bui-space-14)}}.bui-pb{padding-bottom:var(--pb)}.bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.bui-pb-1{padding-bottom:var(--bui-space-1)}.bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.bui-pb-2{padding-bottom:var(--bui-space-2)}.bui-pb-3{padding-bottom:var(--bui-space-3)}.bui-pb-4{padding-bottom:var(--bui-space-4)}.bui-pb-5{padding-bottom:var(--bui-space-5)}.bui-pb-6{padding-bottom:var(--bui-space-6)}.bui-pb-7{padding-bottom:var(--bui-space-7)}.bui-pb-8{padding-bottom:var(--bui-space-8)}.bui-pb-9{padding-bottom:var(--bui-space-9)}.bui-pb-10{padding-bottom:var(--bui-space-10)}.bui-pb-11{padding-bottom:var(--bui-space-11)}.bui-pb-12{padding-bottom:var(--bui-space-12)}.bui-pb-13{padding-bottom:var(--bui-space-13)}.bui-pb-14{padding-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pb{padding-bottom:var(--pb-xs)}.xs\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.xs\:bui-pb-1{padding-bottom:var(--bui-space-1)}.xs\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.xs\:bui-pb-2{padding-bottom:var(--bui-space-2)}.xs\:bui-pb-3{padding-bottom:var(--bui-space-3)}.xs\:bui-pb-4{padding-bottom:var(--bui-space-4)}.xs\:bui-pb-5{padding-bottom:var(--bui-space-5)}.xs\:bui-pb-6{padding-bottom:var(--bui-space-6)}.xs\:bui-pb-7{padding-bottom:var(--bui-space-7)}.xs\:bui-pb-8{padding-bottom:var(--bui-space-8)}.xs\:bui-pb-9{padding-bottom:var(--bui-space-9)}.xs\:bui-pb-10{padding-bottom:var(--bui-space-10)}.xs\:bui-pb-11{padding-bottom:var(--bui-space-11)}.xs\:bui-pb-12{padding-bottom:var(--bui-space-12)}.xs\:bui-pb-13{padding-bottom:var(--bui-space-13)}.xs\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pb{padding-bottom:var(--pb-sm)}.sm\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.sm\:bui-pb-1{padding-bottom:var(--bui-space-1)}.sm\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.sm\:bui-pb-2{padding-bottom:var(--bui-space-2)}.sm\:bui-pb-3{padding-bottom:var(--bui-space-3)}.sm\:bui-pb-4{padding-bottom:var(--bui-space-4)}.sm\:bui-pb-5{padding-bottom:var(--bui-space-5)}.sm\:bui-pb-6{padding-bottom:var(--bui-space-6)}.sm\:bui-pb-7{padding-bottom:var(--bui-space-7)}.sm\:bui-pb-8{padding-bottom:var(--bui-space-8)}.sm\:bui-pb-9{padding-bottom:var(--bui-space-9)}.sm\:bui-pb-10{padding-bottom:var(--bui-space-10)}.sm\:bui-pb-11{padding-bottom:var(--bui-space-11)}.sm\:bui-pb-12{padding-bottom:var(--bui-space-12)}.sm\:bui-pb-13{padding-bottom:var(--bui-space-13)}.sm\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pb{padding-bottom:var(--pb-md)}.md\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.md\:bui-pb-1{padding-bottom:var(--bui-space-1)}.md\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.md\:bui-pb-2{padding-bottom:var(--bui-space-2)}.md\:bui-pb-3{padding-bottom:var(--bui-space-3)}.md\:bui-pb-4{padding-bottom:var(--bui-space-4)}.md\:bui-pb-5{padding-bottom:var(--bui-space-5)}.md\:bui-pb-6{padding-bottom:var(--bui-space-6)}.md\:bui-pb-7{padding-bottom:var(--bui-space-7)}.md\:bui-pb-8{padding-bottom:var(--bui-space-8)}.md\:bui-pb-9{padding-bottom:var(--bui-space-9)}.md\:bui-pb-10{padding-bottom:var(--bui-space-10)}.md\:bui-pb-11{padding-bottom:var(--bui-space-11)}.md\:bui-pb-12{padding-bottom:var(--bui-space-12)}.md\:bui-pb-13{padding-bottom:var(--bui-space-13)}.md\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pb{padding-bottom:var(--pb-lg)}.lg\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.lg\:bui-pb-1{padding-bottom:var(--bui-space-1)}.lg\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.lg\:bui-pb-2{padding-bottom:var(--bui-space-2)}.lg\:bui-pb-3{padding-bottom:var(--bui-space-3)}.lg\:bui-pb-4{padding-bottom:var(--bui-space-4)}.lg\:bui-pb-5{padding-bottom:var(--bui-space-5)}.lg\:bui-pb-6{padding-bottom:var(--bui-space-6)}.lg\:bui-pb-7{padding-bottom:var(--bui-space-7)}.lg\:bui-pb-8{padding-bottom:var(--bui-space-8)}.lg\:bui-pb-9{padding-bottom:var(--bui-space-9)}.lg\:bui-pb-10{padding-bottom:var(--bui-space-10)}.lg\:bui-pb-11{padding-bottom:var(--bui-space-11)}.lg\:bui-pb-12{padding-bottom:var(--bui-space-12)}.lg\:bui-pb-13{padding-bottom:var(--bui-space-13)}.lg\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pb{padding-bottom:var(--pb-xl)}.xl\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.xl\:bui-pb-1{padding-bottom:var(--bui-space-1)}.xl\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.xl\:bui-pb-2{padding-bottom:var(--bui-space-2)}.xl\:bui-pb-3{padding-bottom:var(--bui-space-3)}.xl\:bui-pb-4{padding-bottom:var(--bui-space-4)}.xl\:bui-pb-5{padding-bottom:var(--bui-space-5)}.xl\:bui-pb-6{padding-bottom:var(--bui-space-6)}.xl\:bui-pb-7{padding-bottom:var(--bui-space-7)}.xl\:bui-pb-8{padding-bottom:var(--bui-space-8)}.xl\:bui-pb-9{padding-bottom:var(--bui-space-9)}.xl\:bui-pb-10{padding-bottom:var(--bui-space-10)}.xl\:bui-pb-11{padding-bottom:var(--bui-space-11)}.xl\:bui-pb-12{padding-bottom:var(--bui-space-12)}.xl\:bui-pb-13{padding-bottom:var(--bui-space-13)}.xl\:bui-pb-14{padding-bottom:var(--bui-space-14)}}.bui-py{padding-top:var(--py);padding-bottom:var(--py)}.bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-py{padding-top:var(--py-xs);padding-bottom:var(--py-xs)}.xs\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.xs\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.xs\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.xs\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.xs\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.xs\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.xs\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.xs\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.xs\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.xs\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.xs\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.xs\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.xs\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.xs\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.xs\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.xs\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-py{padding-top:var(--py-sm);padding-bottom:var(--py-sm)}.sm\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.sm\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.sm\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.sm\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.sm\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.sm\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.sm\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.sm\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.sm\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.sm\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.sm\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.sm\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.sm\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.sm\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.sm\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.sm\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-py{padding-top:var(--py-md);padding-bottom:var(--py-md)}.md\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.md\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.md\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.md\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.md\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.md\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.md\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.md\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.md\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.md\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.md\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.md\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.md\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.md\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.md\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.md\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-py{padding-top:var(--py-lg);padding-bottom:var(--py-lg)}.lg\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.lg\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.lg\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.lg\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.lg\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.lg\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.lg\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.lg\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.lg\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.lg\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.lg\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.lg\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.lg\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.lg\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.lg\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.lg\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-py{padding-top:var(--py-xl);padding-bottom:var(--py-xl)}.xl\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.xl\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.xl\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.xl\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.xl\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.xl\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.xl\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.xl\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.xl\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.xl\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.xl\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.xl\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.xl\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.xl\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.xl\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.xl\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}.bui-px{padding-left:var(--px);padding-right:var(--px)}.bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-px{padding-left:var(--px-xs);padding-right:var(--px-xs)}.xs\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.xs\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.xs\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.xs\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.xs\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.xs\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.xs\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.xs\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.xs\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.xs\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.xs\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.xs\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.xs\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.xs\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.xs\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.xs\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-px{padding-left:var(--px-sm);padding-right:var(--px-sm)}.sm\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.sm\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.sm\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.sm\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.sm\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.sm\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.sm\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.sm\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.sm\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.sm\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.sm\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.sm\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.sm\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.sm\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.sm\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.sm\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-px{padding-left:var(--px-md);padding-right:var(--px-md)}.md\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.md\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.md\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.md\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.md\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.md\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.md\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.md\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.md\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.md\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.md\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.md\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.md\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.md\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.md\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.md\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-px{padding-left:var(--px-lg);padding-right:var(--px-lg)}.lg\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.lg\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.lg\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.lg\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.lg\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.lg\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.lg\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.lg\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.lg\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.lg\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.lg\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.lg\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.lg\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.lg\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.lg\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.lg\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-px{padding-left:var(--px-xl);padding-right:var(--px-xl)}.xl\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.xl\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.xl\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.xl\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.xl\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.xl\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.xl\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.xl\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.xl\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.xl\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.xl\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.xl\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.xl\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.xl\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.xl\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.xl\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}.bui-m{margin:var(--m)}.bui-m-0\.5{margin:var(--bui-space-0_5)}.bui-m-1{margin:var(--bui-space-1)}.bui-m-1\.5{margin:var(--bui-space-1_5)}.bui-m-2{margin:var(--bui-space-2)}.bui-m-3{margin:var(--bui-space-3)}.bui-m-4{margin:var(--bui-space-4)}.bui-m-5{margin:var(--bui-space-5)}.bui-m-6{margin:var(--bui-space-6)}.bui-m-7{margin:var(--bui-space-7)}.bui-m-8{margin:var(--bui-space-8)}.bui-m-9{margin:var(--bui-space-9)}.bui-m-10{margin:var(--bui-space-10)}.bui-m-11{margin:var(--bui-space-11)}.bui-m-12{margin:var(--bui-space-12)}.bui-m-13{margin:var(--bui-space-13)}.bui-m-14{margin:var(--bui-space-14)}@media (width>=640px){.xs\:bui-m{margin:var(--p-xs)}.xs\:bui-m-0\.5{margin:var(--bui-space-0_5)}.xs\:bui-m-1{margin:var(--bui-space-1)}.xs\:bui-m-1\.5{margin:var(--bui-space-1_5)}.xs\:bui-m-2{margin:var(--bui-space-2)}.xs\:bui-m-3{margin:var(--bui-space-3)}.xs\:bui-m-4{margin:var(--bui-space-4)}.xs\:bui-m-5{margin:var(--bui-space-5)}.xs\:bui-m-6{margin:var(--bui-space-6)}.xs\:bui-m-7{margin:var(--bui-space-7)}.xs\:bui-m-8{margin:var(--bui-space-8)}.xs\:bui-m-9{margin:var(--bui-space-9)}.xs\:bui-m-10{margin:var(--bui-space-10)}.xs\:bui-m-11{margin:var(--bui-space-11)}.xs\:bui-m-12{margin:var(--bui-space-12)}.xs\:bui-m-13{margin:var(--bui-space-13)}.xs\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-m{margin:var(--p-sm)}.sm\:bui-m-0\.5{margin:var(--bui-space-0_5)}.sm\:bui-m-1{margin:var(--bui-space-1)}.sm\:bui-m-1\.5{margin:var(--bui-space-1_5)}.sm\:bui-m-2{margin:var(--bui-space-2)}.sm\:bui-m-3{margin:var(--bui-space-3)}.sm\:bui-m-4{margin:var(--bui-space-4)}.sm\:bui-m-5{margin:var(--bui-space-5)}.sm\:bui-m-6{margin:var(--bui-space-6)}.sm\:bui-m-7{margin:var(--bui-space-7)}.sm\:bui-m-8{margin:var(--bui-space-8)}.sm\:bui-m-9{margin:var(--bui-space-9)}.sm\:bui-m-10{margin:var(--bui-space-10)}.sm\:bui-m-11{margin:var(--bui-space-11)}.sm\:bui-m-12{margin:var(--bui-space-12)}.sm\:bui-m-13{margin:var(--bui-space-13)}.sm\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-m{margin:var(--p-md)}.md\:bui-m-0\.5{margin:var(--bui-space-0_5)}.md\:bui-m-1{margin:var(--bui-space-1)}.md\:bui-m-1\.5{margin:var(--bui-space-1_5)}.md\:bui-m-2{margin:var(--bui-space-2)}.md\:bui-m-3{margin:var(--bui-space-3)}.md\:bui-m-4{margin:var(--bui-space-4)}.md\:bui-m-5{margin:var(--bui-space-5)}.md\:bui-m-6{margin:var(--bui-space-6)}.md\:bui-m-7{margin:var(--bui-space-7)}.md\:bui-m-8{margin:var(--bui-space-8)}.md\:bui-m-9{margin:var(--bui-space-9)}.md\:bui-m-10{margin:var(--bui-space-10)}.md\:bui-m-11{margin:var(--bui-space-11)}.md\:bui-m-12{margin:var(--bui-space-12)}.md\:bui-m-13{margin:var(--bui-space-13)}.md\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-m{margin:var(--p-lg)}.lg\:bui-m-0\.5{margin:var(--bui-space-0_5)}.lg\:bui-m-1{margin:var(--bui-space-1)}.lg\:bui-m-1\.5{margin:var(--bui-space-1_5)}.lg\:bui-m-2{margin:var(--bui-space-2)}.lg\:bui-m-3{margin:var(--bui-space-3)}.lg\:bui-m-4{margin:var(--bui-space-4)}.lg\:bui-m-5{margin:var(--bui-space-5)}.lg\:bui-m-6{margin:var(--bui-space-6)}.lg\:bui-m-7{margin:var(--bui-space-7)}.lg\:bui-m-8{margin:var(--bui-space-8)}.lg\:bui-m-9{margin:var(--bui-space-9)}.lg\:bui-m-10{margin:var(--bui-space-10)}.lg\:bui-m-11{margin:var(--bui-space-11)}.lg\:bui-m-12{margin:var(--bui-space-12)}.lg\:bui-m-13{margin:var(--bui-space-13)}.lg\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-m{margin:var(--p-xl)}.xl\:bui-m-0\.5{margin:var(--bui-space-0_5)}.xl\:bui-m-1{margin:var(--bui-space-1)}.xl\:bui-m-1\.5{margin:var(--bui-space-1_5)}.xl\:bui-m-2{margin:var(--bui-space-2)}.xl\:bui-m-3{margin:var(--bui-space-3)}.xl\:bui-m-4{margin:var(--bui-space-4)}.xl\:bui-m-5{margin:var(--bui-space-5)}.xl\:bui-m-6{margin:var(--bui-space-6)}.xl\:bui-m-7{margin:var(--bui-space-7)}.xl\:bui-m-8{margin:var(--bui-space-8)}.xl\:bui-m-9{margin:var(--bui-space-9)}.xl\:bui-m-10{margin:var(--bui-space-10)}.xl\:bui-m-11{margin:var(--bui-space-11)}.xl\:bui-m-12{margin:var(--bui-space-12)}.xl\:bui-m-13{margin:var(--bui-space-13)}.xl\:bui-m-14{margin:var(--bui-space-14)}}.bui-ml{margin-left:var(--ml)}.bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.bui-ml-1{margin-left:var(--bui-space-1)}.bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.bui-ml-2{margin-left:var(--bui-space-2)}.bui-ml-3{margin-left:var(--bui-space-3)}.bui-ml-4{margin-left:var(--bui-space-4)}.bui-ml-5{margin-left:var(--bui-space-5)}.bui-ml-6{margin-left:var(--bui-space-6)}.bui-ml-7{margin-left:var(--bui-space-7)}.bui-ml-8{margin-left:var(--bui-space-8)}.bui-ml-9{margin-left:var(--bui-space-9)}.bui-ml-10{margin-left:var(--bui-space-10)}.bui-ml-11{margin-left:var(--bui-space-11)}.bui-ml-12{margin-left:var(--bui-space-12)}.bui-ml-13{margin-left:var(--bui-space-13)}.bui-ml-14{margin-left:var(--bui-space-14)}@media (width>=640px){.xs\:bui-ml{margin-left:var(--ml-xs)}.xs\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.xs\:bui-ml-1{margin-left:var(--bui-space-1)}.xs\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.xs\:bui-ml-2{margin-left:var(--bui-space-2)}.xs\:bui-ml-3{margin-left:var(--bui-space-3)}.xs\:bui-ml-4{margin-left:var(--bui-space-4)}.xs\:bui-ml-5{margin-left:var(--bui-space-5)}.xs\:bui-ml-6{margin-left:var(--bui-space-6)}.xs\:bui-ml-7{margin-left:var(--bui-space-7)}.xs\:bui-ml-8{margin-left:var(--bui-space-8)}.xs\:bui-ml-9{margin-left:var(--bui-space-9)}.xs\:bui-ml-10{margin-left:var(--bui-space-10)}.xs\:bui-ml-11{margin-left:var(--bui-space-11)}.xs\:bui-ml-12{margin-left:var(--bui-space-12)}.xs\:bui-ml-13{margin-left:var(--bui-space-13)}.xs\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-ml{margin-left:var(--ml-sm)}.sm\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.sm\:bui-ml-1{margin-left:var(--bui-space-1)}.sm\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.sm\:bui-ml-2{margin-left:var(--bui-space-2)}.sm\:bui-ml-3{margin-left:var(--bui-space-3)}.sm\:bui-ml-4{margin-left:var(--bui-space-4)}.sm\:bui-ml-5{margin-left:var(--bui-space-5)}.sm\:bui-ml-6{margin-left:var(--bui-space-6)}.sm\:bui-ml-7{margin-left:var(--bui-space-7)}.sm\:bui-ml-8{margin-left:var(--bui-space-8)}.sm\:bui-ml-9{margin-left:var(--bui-space-9)}.sm\:bui-ml-10{margin-left:var(--bui-space-10)}.sm\:bui-ml-11{margin-left:var(--bui-space-11)}.sm\:bui-ml-12{margin-left:var(--bui-space-12)}.sm\:bui-ml-13{margin-left:var(--bui-space-13)}.sm\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-ml{margin-left:var(--ml-md)}.md\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.md\:bui-ml-1{margin-left:var(--bui-space-1)}.md\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.md\:bui-ml-2{margin-left:var(--bui-space-2)}.md\:bui-ml-3{margin-left:var(--bui-space-3)}.md\:bui-ml-4{margin-left:var(--bui-space-4)}.md\:bui-ml-5{margin-left:var(--bui-space-5)}.md\:bui-ml-6{margin-left:var(--bui-space-6)}.md\:bui-ml-7{margin-left:var(--bui-space-7)}.md\:bui-ml-8{margin-left:var(--bui-space-8)}.md\:bui-ml-9{margin-left:var(--bui-space-9)}.md\:bui-ml-10{margin-left:var(--bui-space-10)}.md\:bui-ml-11{margin-left:var(--bui-space-11)}.md\:bui-ml-12{margin-left:var(--bui-space-12)}.md\:bui-ml-13{margin-left:var(--bui-space-13)}.md\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-ml{margin-left:var(--ml-lg)}.lg\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.lg\:bui-ml-1{margin-left:var(--bui-space-1)}.lg\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.lg\:bui-ml-2{margin-left:var(--bui-space-2)}.lg\:bui-ml-3{margin-left:var(--bui-space-3)}.lg\:bui-ml-4{margin-left:var(--bui-space-4)}.lg\:bui-ml-5{margin-left:var(--bui-space-5)}.lg\:bui-ml-6{margin-left:var(--bui-space-6)}.lg\:bui-ml-7{margin-left:var(--bui-space-7)}.lg\:bui-ml-8{margin-left:var(--bui-space-8)}.lg\:bui-ml-9{margin-left:var(--bui-space-9)}.lg\:bui-ml-10{margin-left:var(--bui-space-10)}.lg\:bui-ml-11{margin-left:var(--bui-space-11)}.lg\:bui-ml-12{margin-left:var(--bui-space-12)}.lg\:bui-ml-13{margin-left:var(--bui-space-13)}.lg\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-ml{margin-left:var(--ml-xl)}.xl\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.xl\:bui-ml-1{margin-left:var(--bui-space-1)}.xl\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.xl\:bui-ml-2{margin-left:var(--bui-space-2)}.xl\:bui-ml-3{margin-left:var(--bui-space-3)}.xl\:bui-ml-4{margin-left:var(--bui-space-4)}.xl\:bui-ml-5{margin-left:var(--bui-space-5)}.xl\:bui-ml-6{margin-left:var(--bui-space-6)}.xl\:bui-ml-7{margin-left:var(--bui-space-7)}.xl\:bui-ml-8{margin-left:var(--bui-space-8)}.xl\:bui-ml-9{margin-left:var(--bui-space-9)}.xl\:bui-ml-10{margin-left:var(--bui-space-10)}.xl\:bui-ml-11{margin-left:var(--bui-space-11)}.xl\:bui-ml-12{margin-left:var(--bui-space-12)}.xl\:bui-ml-13{margin-left:var(--bui-space-13)}.xl\:bui-ml-14{margin-left:var(--bui-space-14)}}.bui-mr{margin-right:var(--mr)}.bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.bui-mr-1{margin-right:var(--bui-space-1)}.bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.bui-mr-2{margin-right:var(--bui-space-2)}.bui-mr-3{margin-right:var(--bui-space-3)}.bui-mr-4{margin-right:var(--bui-space-4)}.bui-mr-5{margin-right:var(--bui-space-5)}.bui-mr-6{margin-right:var(--bui-space-6)}.bui-mr-7{margin-right:var(--bui-space-7)}.bui-mr-8{margin-right:var(--bui-space-8)}.bui-mr-9{margin-right:var(--bui-space-9)}.bui-mr-10{margin-right:var(--bui-space-10)}.bui-mr-11{margin-right:var(--bui-space-11)}.bui-mr-12{margin-right:var(--bui-space-12)}.bui-mr-13{margin-right:var(--bui-space-13)}.bui-mr-14{margin-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mr{margin-right:var(--mr-xs)}.xs\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.xs\:bui-mr-1{margin-right:var(--bui-space-1)}.xs\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.xs\:bui-mr-2{margin-right:var(--bui-space-2)}.xs\:bui-mr-3{margin-right:var(--bui-space-3)}.xs\:bui-mr-4{margin-right:var(--bui-space-4)}.xs\:bui-mr-5{margin-right:var(--bui-space-5)}.xs\:bui-mr-6{margin-right:var(--bui-space-6)}.xs\:bui-mr-7{margin-right:var(--bui-space-7)}.xs\:bui-mr-8{margin-right:var(--bui-space-8)}.xs\:bui-mr-9{margin-right:var(--bui-space-9)}.xs\:bui-mr-10{margin-right:var(--bui-space-10)}.xs\:bui-mr-11{margin-right:var(--bui-space-11)}.xs\:bui-mr-12{margin-right:var(--bui-space-12)}.xs\:bui-mr-13{margin-right:var(--bui-space-13)}.xs\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mr{margin-right:var(--mr-sm)}.sm\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.sm\:bui-mr-1{margin-right:var(--bui-space-1)}.sm\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.sm\:bui-mr-2{margin-right:var(--bui-space-2)}.sm\:bui-mr-3{margin-right:var(--bui-space-3)}.sm\:bui-mr-4{margin-right:var(--bui-space-4)}.sm\:bui-mr-5{margin-right:var(--bui-space-5)}.sm\:bui-mr-6{margin-right:var(--bui-space-6)}.sm\:bui-mr-7{margin-right:var(--bui-space-7)}.sm\:bui-mr-8{margin-right:var(--bui-space-8)}.sm\:bui-mr-9{margin-right:var(--bui-space-9)}.sm\:bui-mr-10{margin-right:var(--bui-space-10)}.sm\:bui-mr-11{margin-right:var(--bui-space-11)}.sm\:bui-mr-12{margin-right:var(--bui-space-12)}.sm\:bui-mr-13{margin-right:var(--bui-space-13)}.sm\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mr{margin-right:var(--mr-md)}.md\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.md\:bui-mr-1{margin-right:var(--bui-space-1)}.md\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.md\:bui-mr-2{margin-right:var(--bui-space-2)}.md\:bui-mr-3{margin-right:var(--bui-space-3)}.md\:bui-mr-4{margin-right:var(--bui-space-4)}.md\:bui-mr-5{margin-right:var(--bui-space-5)}.md\:bui-mr-6{margin-right:var(--bui-space-6)}.md\:bui-mr-7{margin-right:var(--bui-space-7)}.md\:bui-mr-8{margin-right:var(--bui-space-8)}.md\:bui-mr-9{margin-right:var(--bui-space-9)}.md\:bui-mr-10{margin-right:var(--bui-space-10)}.md\:bui-mr-11{margin-right:var(--bui-space-11)}.md\:bui-mr-12{margin-right:var(--bui-space-12)}.md\:bui-mr-13{margin-right:var(--bui-space-13)}.md\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mr{margin-right:var(--mr-lg)}.lg\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.lg\:bui-mr-1{margin-right:var(--bui-space-1)}.lg\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.lg\:bui-mr-2{margin-right:var(--bui-space-2)}.lg\:bui-mr-3{margin-right:var(--bui-space-3)}.lg\:bui-mr-4{margin-right:var(--bui-space-4)}.lg\:bui-mr-5{margin-right:var(--bui-space-5)}.lg\:bui-mr-6{margin-right:var(--bui-space-6)}.lg\:bui-mr-7{margin-right:var(--bui-space-7)}.lg\:bui-mr-8{margin-right:var(--bui-space-8)}.lg\:bui-mr-9{margin-right:var(--bui-space-9)}.lg\:bui-mr-10{margin-right:var(--bui-space-10)}.lg\:bui-mr-11{margin-right:var(--bui-space-11)}.lg\:bui-mr-12{margin-right:var(--bui-space-12)}.lg\:bui-mr-13{margin-right:var(--bui-space-13)}.lg\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mr{margin-right:var(--mr-xl)}.xl\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.xl\:bui-mr-1{margin-right:var(--bui-space-1)}.xl\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.xl\:bui-mr-2{margin-right:var(--bui-space-2)}.xl\:bui-mr-3{margin-right:var(--bui-space-3)}.xl\:bui-mr-4{margin-right:var(--bui-space-4)}.xl\:bui-mr-5{margin-right:var(--bui-space-5)}.xl\:bui-mr-6{margin-right:var(--bui-space-6)}.xl\:bui-mr-7{margin-right:var(--bui-space-7)}.xl\:bui-mr-8{margin-right:var(--bui-space-8)}.xl\:bui-mr-9{margin-right:var(--bui-space-9)}.xl\:bui-mr-10{margin-right:var(--bui-space-10)}.xl\:bui-mr-11{margin-right:var(--bui-space-11)}.xl\:bui-mr-12{margin-right:var(--bui-space-12)}.xl\:bui-mr-13{margin-right:var(--bui-space-13)}.xl\:bui-mr-14{margin-right:var(--bui-space-14)}}.bui-mt{margin-top:var(--mt)}.bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.bui-mt-1{margin-top:var(--bui-space-1)}.bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.bui-mt-2{margin-top:var(--bui-space-2)}.bui-mt-3{margin-top:var(--bui-space-3)}.bui-mt-4{margin-top:var(--bui-space-4)}.bui-mt-5{margin-top:var(--bui-space-5)}.bui-mt-6{margin-top:var(--bui-space-6)}.bui-mt-7{margin-top:var(--bui-space-7)}.bui-mt-8{margin-top:var(--bui-space-8)}.bui-mt-9{margin-top:var(--bui-space-9)}.bui-mt-10{margin-top:var(--bui-space-10)}.bui-mt-11{margin-top:var(--bui-space-11)}.bui-mt-12{margin-top:var(--bui-space-12)}.bui-mt-13{margin-top:var(--bui-space-13)}.bui-mt-14{margin-top:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mt{margin-top:var(--mt-xs)}.xs\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.xs\:bui-mt-1{margin-top:var(--bui-space-1)}.xs\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.xs\:bui-mt-2{margin-top:var(--bui-space-2)}.xs\:bui-mt-3{margin-top:var(--bui-space-3)}.xs\:bui-mt-4{margin-top:var(--bui-space-4)}.xs\:bui-mt-5{margin-top:var(--bui-space-5)}.xs\:bui-mt-6{margin-top:var(--bui-space-6)}.xs\:bui-mt-7{margin-top:var(--bui-space-7)}.xs\:bui-mt-8{margin-top:var(--bui-space-8)}.xs\:bui-mt-9{margin-top:var(--bui-space-9)}.xs\:bui-mt-10{margin-top:var(--bui-space-10)}.xs\:bui-mt-11{margin-top:var(--bui-space-11)}.xs\:bui-mt-12{margin-top:var(--bui-space-12)}.xs\:bui-mt-13{margin-top:var(--bui-space-13)}.xs\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mt{margin-top:var(--mt-sm)}.sm\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.sm\:bui-mt-1{margin-top:var(--bui-space-1)}.sm\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.sm\:bui-mt-2{margin-top:var(--bui-space-2)}.sm\:bui-mt-3{margin-top:var(--bui-space-3)}.sm\:bui-mt-4{margin-top:var(--bui-space-4)}.sm\:bui-mt-5{margin-top:var(--bui-space-5)}.sm\:bui-mt-6{margin-top:var(--bui-space-6)}.sm\:bui-mt-7{margin-top:var(--bui-space-7)}.sm\:bui-mt-8{margin-top:var(--bui-space-8)}.sm\:bui-mt-9{margin-top:var(--bui-space-9)}.sm\:bui-mt-10{margin-top:var(--bui-space-10)}.sm\:bui-mt-11{margin-top:var(--bui-space-11)}.sm\:bui-mt-12{margin-top:var(--bui-space-12)}.sm\:bui-mt-13{margin-top:var(--bui-space-13)}.sm\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mt{margin-top:var(--mt-md)}.md\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.md\:bui-mt-1{margin-top:var(--bui-space-1)}.md\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.md\:bui-mt-2{margin-top:var(--bui-space-2)}.md\:bui-mt-3{margin-top:var(--bui-space-3)}.md\:bui-mt-4{margin-top:var(--bui-space-4)}.md\:bui-mt-5{margin-top:var(--bui-space-5)}.md\:bui-mt-6{margin-top:var(--bui-space-6)}.md\:bui-mt-7{margin-top:var(--bui-space-7)}.md\:bui-mt-8{margin-top:var(--bui-space-8)}.md\:bui-mt-9{margin-top:var(--bui-space-9)}.md\:bui-mt-10{margin-top:var(--bui-space-10)}.md\:bui-mt-11{margin-top:var(--bui-space-11)}.md\:bui-mt-12{margin-top:var(--bui-space-12)}.md\:bui-mt-13{margin-top:var(--bui-space-13)}.md\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mt{margin-top:var(--mt-lg)}.lg\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.lg\:bui-mt-1{margin-top:var(--bui-space-1)}.lg\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.lg\:bui-mt-2{margin-top:var(--bui-space-2)}.lg\:bui-mt-3{margin-top:var(--bui-space-3)}.lg\:bui-mt-4{margin-top:var(--bui-space-4)}.lg\:bui-mt-5{margin-top:var(--bui-space-5)}.lg\:bui-mt-6{margin-top:var(--bui-space-6)}.lg\:bui-mt-7{margin-top:var(--bui-space-7)}.lg\:bui-mt-8{margin-top:var(--bui-space-8)}.lg\:bui-mt-9{margin-top:var(--bui-space-9)}.lg\:bui-mt-10{margin-top:var(--bui-space-10)}.lg\:bui-mt-11{margin-top:var(--bui-space-11)}.lg\:bui-mt-12{margin-top:var(--bui-space-12)}.lg\:bui-mt-13{margin-top:var(--bui-space-13)}.lg\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mt{margin-top:var(--mt-xl)}.xl\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.xl\:bui-mt-1{margin-top:var(--bui-space-1)}.xl\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.xl\:bui-mt-2{margin-top:var(--bui-space-2)}.xl\:bui-mt-3{margin-top:var(--bui-space-3)}.xl\:bui-mt-4{margin-top:var(--bui-space-4)}.xl\:bui-mt-5{margin-top:var(--bui-space-5)}.xl\:bui-mt-6{margin-top:var(--bui-space-6)}.xl\:bui-mt-7{margin-top:var(--bui-space-7)}.xl\:bui-mt-8{margin-top:var(--bui-space-8)}.xl\:bui-mt-9{margin-top:var(--bui-space-9)}.xl\:bui-mt-10{margin-top:var(--bui-space-10)}.xl\:bui-mt-11{margin-top:var(--bui-space-11)}.xl\:bui-mt-12{margin-top:var(--bui-space-12)}.xl\:bui-mt-13{margin-top:var(--bui-space-13)}.xl\:bui-mt-14{margin-top:var(--bui-space-14)}}.bui-mb{margin-bottom:var(--mb)}.bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.bui-mb-1{margin-bottom:var(--bui-space-1)}.bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.bui-mb-2{margin-bottom:var(--bui-space-2)}.bui-mb-3{margin-bottom:var(--bui-space-3)}.bui-mb-4{margin-bottom:var(--bui-space-4)}.bui-mb-5{margin-bottom:var(--bui-space-5)}.bui-mb-6{margin-bottom:var(--bui-space-6)}.bui-mb-7{margin-bottom:var(--bui-space-7)}.bui-mb-8{margin-bottom:var(--bui-space-8)}.bui-mb-9{margin-bottom:var(--bui-space-9)}.bui-mb-10{margin-bottom:var(--bui-space-10)}.bui-mb-11{margin-bottom:var(--bui-space-11)}.bui-mb-12{margin-bottom:var(--bui-space-12)}.bui-mb-13{margin-bottom:var(--bui-space-13)}.bui-mb-14{margin-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mb{margin-bottom:var(--mb-xs)}.xs\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.xs\:bui-mb-1{margin-bottom:var(--bui-space-1)}.xs\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.xs\:bui-mb-2{margin-bottom:var(--bui-space-2)}.xs\:bui-mb-3{margin-bottom:var(--bui-space-3)}.xs\:bui-mb-4{margin-bottom:var(--bui-space-4)}.xs\:bui-mb-5{margin-bottom:var(--bui-space-5)}.xs\:bui-mb-6{margin-bottom:var(--bui-space-6)}.xs\:bui-mb-7{margin-bottom:var(--bui-space-7)}.xs\:bui-mb-8{margin-bottom:var(--bui-space-8)}.xs\:bui-mb-9{margin-bottom:var(--bui-space-9)}.xs\:bui-mb-10{margin-bottom:var(--bui-space-10)}.xs\:bui-mb-11{margin-bottom:var(--bui-space-11)}.xs\:bui-mb-12{margin-bottom:var(--bui-space-12)}.xs\:bui-mb-13{margin-bottom:var(--bui-space-13)}.xs\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mb{margin-bottom:var(--mb-sm)}.sm\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.sm\:bui-mb-1{margin-bottom:var(--bui-space-1)}.sm\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.sm\:bui-mb-2{margin-bottom:var(--bui-space-2)}.sm\:bui-mb-3{margin-bottom:var(--bui-space-3)}.sm\:bui-mb-4{margin-bottom:var(--bui-space-4)}.sm\:bui-mb-5{margin-bottom:var(--bui-space-5)}.sm\:bui-mb-6{margin-bottom:var(--bui-space-6)}.sm\:bui-mb-7{margin-bottom:var(--bui-space-7)}.sm\:bui-mb-8{margin-bottom:var(--bui-space-8)}.sm\:bui-mb-9{margin-bottom:var(--bui-space-9)}.sm\:bui-mb-10{margin-bottom:var(--bui-space-10)}.sm\:bui-mb-11{margin-bottom:var(--bui-space-11)}.sm\:bui-mb-12{margin-bottom:var(--bui-space-12)}.sm\:bui-mb-13{margin-bottom:var(--bui-space-13)}.sm\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mb{margin-bottom:var(--mb-md)}.md\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.md\:bui-mb-1{margin-bottom:var(--bui-space-1)}.md\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.md\:bui-mb-2{margin-bottom:var(--bui-space-2)}.md\:bui-mb-3{margin-bottom:var(--bui-space-3)}.md\:bui-mb-4{margin-bottom:var(--bui-space-4)}.md\:bui-mb-5{margin-bottom:var(--bui-space-5)}.md\:bui-mb-6{margin-bottom:var(--bui-space-6)}.md\:bui-mb-7{margin-bottom:var(--bui-space-7)}.md\:bui-mb-8{margin-bottom:var(--bui-space-8)}.md\:bui-mb-9{margin-bottom:var(--bui-space-9)}.md\:bui-mb-10{margin-bottom:var(--bui-space-10)}.md\:bui-mb-11{margin-bottom:var(--bui-space-11)}.md\:bui-mb-12{margin-bottom:var(--bui-space-12)}.md\:bui-mb-13{margin-bottom:var(--bui-space-13)}.md\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mb{margin-bottom:var(--mb-lg)}.lg\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.lg\:bui-mb-1{margin-bottom:var(--bui-space-1)}.lg\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.lg\:bui-mb-2{margin-bottom:var(--bui-space-2)}.lg\:bui-mb-3{margin-bottom:var(--bui-space-3)}.lg\:bui-mb-4{margin-bottom:var(--bui-space-4)}.lg\:bui-mb-5{margin-bottom:var(--bui-space-5)}.lg\:bui-mb-6{margin-bottom:var(--bui-space-6)}.lg\:bui-mb-7{margin-bottom:var(--bui-space-7)}.lg\:bui-mb-8{margin-bottom:var(--bui-space-8)}.lg\:bui-mb-9{margin-bottom:var(--bui-space-9)}.lg\:bui-mb-10{margin-bottom:var(--bui-space-10)}.lg\:bui-mb-11{margin-bottom:var(--bui-space-11)}.lg\:bui-mb-12{margin-bottom:var(--bui-space-12)}.lg\:bui-mb-13{margin-bottom:var(--bui-space-13)}.lg\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mb{margin-bottom:var(--mb-xl)}.xl\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.xl\:bui-mb-1{margin-bottom:var(--bui-space-1)}.xl\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.xl\:bui-mb-2{margin-bottom:var(--bui-space-2)}.xl\:bui-mb-3{margin-bottom:var(--bui-space-3)}.xl\:bui-mb-4{margin-bottom:var(--bui-space-4)}.xl\:bui-mb-5{margin-bottom:var(--bui-space-5)}.xl\:bui-mb-6{margin-bottom:var(--bui-space-6)}.xl\:bui-mb-7{margin-bottom:var(--bui-space-7)}.xl\:bui-mb-8{margin-bottom:var(--bui-space-8)}.xl\:bui-mb-9{margin-bottom:var(--bui-space-9)}.xl\:bui-mb-10{margin-bottom:var(--bui-space-10)}.xl\:bui-mb-11{margin-bottom:var(--bui-space-11)}.xl\:bui-mb-12{margin-bottom:var(--bui-space-12)}.xl\:bui-mb-13{margin-bottom:var(--bui-space-13)}.xl\:bui-mb-14{margin-bottom:var(--bui-space-14)}}.bui-my{margin-top:var(--my);margin-bottom:var(--my)}.bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-my{margin-top:var(--my-xs);margin-bottom:var(--my-xs)}.xs\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.xs\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.xs\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.xs\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.xs\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.xs\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.xs\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.xs\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.xs\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.xs\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.xs\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.xs\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.xs\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.xs\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.xs\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.xs\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-my{margin-top:var(--my-sm);margin-bottom:var(--my-sm)}.sm\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.sm\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.sm\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.sm\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.sm\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.sm\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.sm\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.sm\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.sm\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.sm\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.sm\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.sm\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.sm\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.sm\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.sm\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.sm\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-my{margin-top:var(--my-md);margin-bottom:var(--my-md)}.md\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.md\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.md\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.md\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.md\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.md\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.md\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.md\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.md\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.md\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.md\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.md\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.md\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.md\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.md\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.md\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-my{margin-top:var(--my-lg);margin-bottom:var(--my-lg)}.lg\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.lg\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.lg\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.lg\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.lg\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.lg\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.lg\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.lg\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.lg\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.lg\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.lg\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.lg\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.lg\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.lg\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.lg\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.lg\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-my{margin-top:var(--my-xl);margin-bottom:var(--my-xl)}.xl\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.xl\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.xl\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.xl\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.xl\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.xl\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.xl\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.xl\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.xl\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.xl\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.xl\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.xl\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.xl\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.xl\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.xl\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.xl\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}.bui-mx{margin-left:var(--mx);margin-right:var(--mx)}.bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mx{margin-left:var(--mx-xs);margin-right:var(--mx-xs)}.xs\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.xs\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.xs\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.xs\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.xs\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.xs\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.xs\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.xs\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.xs\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.xs\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.xs\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.xs\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.xs\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.xs\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.xs\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.xs\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mx{margin-left:var(--mx-sm);margin-right:var(--mx-sm)}.sm\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.sm\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.sm\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.sm\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.sm\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.sm\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.sm\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.sm\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.sm\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.sm\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.sm\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.sm\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.sm\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.sm\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.sm\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.sm\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mx{margin-left:var(--mx-md);margin-right:var(--mx-md)}.md\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.md\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.md\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.md\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.md\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.md\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.md\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.md\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.md\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.md\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.md\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.md\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.md\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.md\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.md\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.md\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mx{margin-left:var(--mx-lg);margin-right:var(--mx-lg)}.lg\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.lg\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.lg\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.lg\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.lg\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.lg\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.lg\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.lg\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.lg\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.lg\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.lg\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.lg\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.lg\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.lg\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.lg\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.lg\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mx{margin-left:var(--mx-xl);margin-right:var(--mx-xl)}.xl\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.xl\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.xl\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.xl\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.xl\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.xl\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.xl\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.xl\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.xl\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.xl\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.xl\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.xl\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.xl\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.xl\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.xl\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.xl\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}.bui-display-none{display:none}.bui-display-inline{display:inline}.bui-display-inline-block{display:inline-block}.bui-display-block{display:block}@media (width>=640px){.xs\:bui-display-none{display:none}.xs\:bui-display-inline{display:inline}.xs\:bui-display-inline-block{display:inline-block}.xs\:bui-display-block{display:block}}@media (width>=768px){.sm\:bui-display-none{display:none}.sm\:bui-display-inline{display:inline}.sm\:bui-display-inline-block{display:inline-block}.sm\:bui-display-block{display:block}}@media (width>=1024px){.md\:bui-display-none{display:none}.md\:bui-display-inline{display:inline}.md\:bui-display-inline-block{display:inline-block}.md\:bui-display-block{display:block}}@media (width>=1280px){.lg\:bui-display-none{display:none}.lg\:bui-display-inline{display:inline}.lg\:bui-display-inline-block{display:inline-block}.lg\:bui-display-block{display:block}}@media (width>=1536px){.xl\:bui-display-none{display:none}.xl\:bui-display-inline{display:inline}.xl\:bui-display-inline-block{display:inline-block}.xl\:bui-display-block{display:block}}.bui-w{width:var(--width)}.bui-min-w{min-width:var(--min-width)}.bui-max-w{max-width:var(--max-width)}@media (width>=640px){.xs\:bui-w{width:var(--width)}.xs\:bui-min-w{min-width:var(--min-width)}.xs\:bui-max-w{max-width:var(--max-width)}}@media (width>=768px){.sm\:bui-w{width:var(--width)}.sm\:bui-min-w{min-width:var(--min-width)}.sm\:bui-max-w{max-width:var(--max-width)}}@media (width>=1024px){.md\:bui-w{width:var(--width)}.md\:bui-min-w{min-width:var(--min-width)}.md\:bui-max-w{max-width:var(--max-width)}}@media (width>=1280px){.lg\:bui-w{width:var(--width)}.lg\:bui-min-w{min-width:var(--min-width)}.lg\:bui-max-w{max-width:var(--max-width)}}@media (width>=1536px){.xl\:bui-w{width:var(--width)}.xl\:bui-min-w{min-width:var(--min-width)}.xl\:bui-max-w{max-width:var(--max-width)}}.bui-h{height:var(--height)}.bui-min-h{min-height:var(--min-height)}.bui-max-h{max-height:var(--max-height)}@media (width>=640px){.xs\:bui-h{height:var(--height)}.xs\:bui-min-h{min-height:var(--min-height)}.xs\:bui-max-h{max-height:var(--max-height)}}@media (width>=768px){.sm\:bui-h{height:var(--height)}.sm\:bui-min-h{min-height:var(--min-height)}.sm\:bui-max-h{max-height:var(--max-height)}}@media (width>=1024px){.md\:bui-h{height:var(--height)}.md\:bui-min-h{min-height:var(--min-height)}.md\:bui-max-h{max-height:var(--max-height)}}@media (width>=1280px){.lg\:bui-h{height:var(--height)}.lg\:bui-min-h{min-height:var(--min-height)}.lg\:bui-max-h{max-height:var(--max-height)}}@media (width>=1536px){.xl\:bui-h{height:var(--height)}.xl\:bui-min-h{min-height:var(--min-height)}.xl\:bui-max-h{max-height:var(--max-height)}}.bui-position-absolute{position:absolute}.bui-position-fixed{position:fixed}.bui-position-sticky{position:sticky}.bui-position-relative{position:relative}.bui-position-static{position:static}@media (width>=640px){.xs\:bui-position-absolute{position:absolute}.xs\:bui-position-fixed{position:fixed}.xs\:bui-position-sticky{position:sticky}.xs\:bui-position-relative{position:relative}.xs\:bui-position-static{position:static}}@media (width>=768px){.sm\:bui-position-absolute{position:absolute}.sm\:bui-position-fixed{position:fixed}.sm\:bui-position-sticky{position:sticky}.sm\:bui-position-relative{position:relative}.sm\:bui-position-static{position:static}}@media (width>=1024px){.md\:bui-position-absolute{position:absolute}.md\:bui-position-fixed{position:fixed}.md\:bui-position-sticky{position:sticky}.md\:bui-position-relative{position:relative}.md\:bui-position-static{position:static}}@media (width>=1280px){.lg\:bui-position-absolute{position:absolute}.lg\:bui-position-fixed{position:fixed}.lg\:bui-position-sticky{position:sticky}.lg\:bui-position-relative{position:relative}.lg\:bui-position-static{position:static}}@media (width>=1536px){.xl\:bui-position-absolute{position:absolute}.xl\:bui-position-fixed{position:fixed}.xl\:bui-position-sticky{position:sticky}.xl\:bui-position-relative{position:relative}.xl\:bui-position-static{position:static}}.bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.bui-col-span-1{grid-column:span 1/span 1}.bui-col-span-2{grid-column:span 2/span 2}.bui-col-span-3{grid-column:span 3/span 3}.bui-col-span-4{grid-column:span 4/span 4}.bui-col-span-5{grid-column:span 5/span 5}.bui-col-span-6{grid-column:span 6/span 6}.bui-col-span-7{grid-column:span 7/span 7}.bui-col-span-8{grid-column:span 8/span 8}.bui-col-span-9{grid-column:span 9/span 9}.bui-col-span-10{grid-column:span 10/span 10}.bui-col-span-11{grid-column:span 11/span 11}.bui-col-span-12{grid-column:span 12/span 12}.bui-col-span-auto{grid-column:span auto/span auto}.bui-col-start-1{grid-column-start:1}.bui-col-start-2{grid-column-start:2}.bui-col-start-3{grid-column-start:3}.bui-col-start-4{grid-column-start:4}.bui-col-start-5{grid-column-start:5}.bui-col-start-6{grid-column-start:6}.bui-col-start-7{grid-column-start:7}.bui-col-start-8{grid-column-start:8}.bui-col-start-9{grid-column-start:9}.bui-col-start-10{grid-column-start:10}.bui-col-start-11{grid-column-start:11}.bui-col-start-12{grid-column-start:12}.bui-col-start-13{grid-column-start:13}.bui-col-start-auto{grid-column-start:auto}.bui-col-end-1{grid-column-end:1}.bui-col-end-2{grid-column-end:2}.bui-col-end-3{grid-column-end:3}.bui-col-end-4{grid-column-end:4}.bui-col-end-5{grid-column-end:5}.bui-col-end-6{grid-column-end:6}.bui-col-end-7{grid-column-end:7}.bui-col-end-8{grid-column-end:8}.bui-col-end-9{grid-column-end:9}.bui-col-end-10{grid-column-end:10}.bui-col-end-11{grid-column-end:11}.bui-col-end-12{grid-column-end:12}.bui-col-end-13{grid-column-end:13}.bui-col-end-auto{grid-column-end:auto}.bui-row-span-1{grid-row:span 1/span 1}.bui-row-span-2{grid-row:span 2/span 2}.bui-row-span-3{grid-row:span 3/span 3}.bui-row-span-4{grid-row:span 4/span 4}.bui-row-span-5{grid-row:span 5/span 5}.bui-row-span-6{grid-row:span 6/span 6}.bui-row-span-7{grid-row:span 7/span 7}.bui-row-span-8{grid-row:span 8/span 8}.bui-row-span-9{grid-row:span 9/span 9}.bui-row-span-10{grid-row:span 10/span 10}.bui-row-span-11{grid-row:span 11/span 11}.bui-row-span-12{grid-row:span 12/span 12}.bui-row-span-auto{grid-row:span auto/span auto}@media (width>=640px){.xs\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xs\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xs\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xs\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xs\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xs\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xs\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.xs\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.xs\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.xs\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xs\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.xs\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xs\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.xs\:bui-col-span-1{grid-column:span 1/span 1}.xs\:bui-col-span-2{grid-column:span 2/span 2}.xs\:bui-col-span-3{grid-column:span 3/span 3}.xs\:bui-col-span-4{grid-column:span 4/span 4}.xs\:bui-col-span-5{grid-column:span 5/span 5}.xs\:bui-col-span-6{grid-column:span 6/span 6}.xs\:bui-col-span-7{grid-column:span 7/span 7}.xs\:bui-col-span-8{grid-column:span 8/span 8}.xs\:bui-col-span-9{grid-column:span 9/span 9}.xs\:bui-col-span-10{grid-column:span 10/span 10}.xs\:bui-col-span-11{grid-column:span 11/span 11}.xs\:bui-col-span-12{grid-column:span 12/span 12}.xs\:bui-col-span-auto{grid-column:span auto/span auto}.xs\:bui-col-start-1{grid-column-start:1}.xs\:bui-col-start-2{grid-column-start:2}.xs\:bui-col-start-3{grid-column-start:3}.xs\:bui-col-start-4{grid-column-start:4}.xs\:bui-col-start-5{grid-column-start:5}.xs\:bui-col-start-6{grid-column-start:6}.xs\:bui-col-start-7{grid-column-start:7}.xs\:bui-col-start-8{grid-column-start:8}.xs\:bui-col-start-9{grid-column-start:9}.xs\:bui-col-start-10{grid-column-start:10}.xs\:bui-col-start-11{grid-column-start:11}.xs\:bui-col-start-12{grid-column-start:12}.xs\:bui-col-start-13{grid-column-start:13}.xs\:bui-col-start-auto{grid-column-start:auto}.xs\:bui-col-end-1{grid-column-end:1}.xs\:bui-col-end-2{grid-column-end:2}.xs\:bui-col-end-3{grid-column-end:3}.xs\:bui-col-end-4{grid-column-end:4}.xs\:bui-col-end-5{grid-column-end:5}.xs\:bui-col-end-6{grid-column-end:6}.xs\:bui-col-end-7{grid-column-end:7}.xs\:bui-col-end-8{grid-column-end:8}.xs\:bui-col-end-9{grid-column-end:9}.xs\:bui-col-end-10{grid-column-end:10}.xs\:bui-col-end-11{grid-column-end:11}.xs\:bui-col-end-12{grid-column-end:12}.xs\:bui-col-end-13{grid-column-end:13}.xs\:bui-col-end-auto{grid-column-end:auto}.xs\:bui-row-span-1{grid-row:span 1/span 1}.xs\:bui-row-span-2{grid-row:span 2/span 2}.xs\:bui-row-span-3{grid-row:span 3/span 3}.xs\:bui-row-span-4{grid-row:span 4/span 4}.xs\:bui-row-span-5{grid-row:span 5/span 5}.xs\:bui-row-span-6{grid-row:span 6/span 6}.xs\:bui-row-span-7{grid-row:span 7/span 7}.xs\:bui-row-span-8{grid-row:span 8/span 8}.xs\:bui-row-span-9{grid-row:span 9/span 9}.xs\:bui-row-span-10{grid-row:span 10/span 10}.xs\:bui-row-span-11{grid-row:span 11/span 11}.xs\:bui-row-span-12{grid-row:span 12/span 12}.xs\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=768px){.sm\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.sm\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.sm\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.sm\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.sm\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.sm\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.sm\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.sm\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.sm\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.sm\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.sm\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.sm\:bui-col-span-1{grid-column:span 1/span 1}.sm\:bui-col-span-2{grid-column:span 2/span 2}.sm\:bui-col-span-3{grid-column:span 3/span 3}.sm\:bui-col-span-4{grid-column:span 4/span 4}.sm\:bui-col-span-5{grid-column:span 5/span 5}.sm\:bui-col-span-6{grid-column:span 6/span 6}.sm\:bui-col-span-7{grid-column:span 7/span 7}.sm\:bui-col-span-8{grid-column:span 8/span 8}.sm\:bui-col-span-9{grid-column:span 9/span 9}.sm\:bui-col-span-10{grid-column:span 10/span 10}.sm\:bui-col-span-11{grid-column:span 11/span 11}.sm\:bui-col-span-12{grid-column:span 12/span 12}.sm\:bui-col-span-auto{grid-column:span auto/span auto}.sm\:bui-col-start-1{grid-column-start:1}.sm\:bui-col-start-2{grid-column-start:2}.sm\:bui-col-start-3{grid-column-start:3}.sm\:bui-col-start-4{grid-column-start:4}.sm\:bui-col-start-5{grid-column-start:5}.sm\:bui-col-start-6{grid-column-start:6}.sm\:bui-col-start-7{grid-column-start:7}.sm\:bui-col-start-8{grid-column-start:8}.sm\:bui-col-start-9{grid-column-start:9}.sm\:bui-col-start-10{grid-column-start:10}.sm\:bui-col-start-11{grid-column-start:11}.sm\:bui-col-start-12{grid-column-start:12}.sm\:bui-col-start-13{grid-column-start:13}.sm\:bui-col-start-auto{grid-column-start:auto}.sm\:bui-col-end-1{grid-column-end:1}.sm\:bui-col-end-2{grid-column-end:2}.sm\:bui-col-end-3{grid-column-end:3}.sm\:bui-col-end-4{grid-column-end:4}.sm\:bui-col-end-5{grid-column-end:5}.sm\:bui-col-end-6{grid-column-end:6}.sm\:bui-col-end-7{grid-column-end:7}.sm\:bui-col-end-8{grid-column-end:8}.sm\:bui-col-end-9{grid-column-end:9}.sm\:bui-col-end-10{grid-column-end:10}.sm\:bui-col-end-11{grid-column-end:11}.sm\:bui-col-end-12{grid-column-end:12}.sm\:bui-col-end-13{grid-column-end:13}.sm\:bui-col-end-auto{grid-column-end:auto}.sm\:bui-row-span-1{grid-row:span 1/span 1}.sm\:bui-row-span-2{grid-row:span 2/span 2}.sm\:bui-row-span-3{grid-row:span 3/span 3}.sm\:bui-row-span-4{grid-row:span 4/span 4}.sm\:bui-row-span-5{grid-row:span 5/span 5}.sm\:bui-row-span-6{grid-row:span 6/span 6}.sm\:bui-row-span-7{grid-row:span 7/span 7}.sm\:bui-row-span-8{grid-row:span 8/span 8}.sm\:bui-row-span-9{grid-row:span 9/span 9}.sm\:bui-row-span-10{grid-row:span 10/span 10}.sm\:bui-row-span-11{grid-row:span 11/span 11}.sm\:bui-row-span-12{grid-row:span 12/span 12}.sm\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1024px){.md\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.md\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.md\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.md\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.md\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.md\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.md\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.md\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.md\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.md\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.md\:bui-col-span-1{grid-column:span 1/span 1}.md\:bui-col-span-2{grid-column:span 2/span 2}.md\:bui-col-span-3{grid-column:span 3/span 3}.md\:bui-col-span-4{grid-column:span 4/span 4}.md\:bui-col-span-5{grid-column:span 5/span 5}.md\:bui-col-span-6{grid-column:span 6/span 6}.md\:bui-col-span-7{grid-column:span 7/span 7}.md\:bui-col-span-8{grid-column:span 8/span 8}.md\:bui-col-span-9{grid-column:span 9/span 9}.md\:bui-col-span-10{grid-column:span 10/span 10}.md\:bui-col-span-11{grid-column:span 11/span 11}.md\:bui-col-span-12{grid-column:span 12/span 12}.md\:bui-col-span-auto{grid-column:span auto/span auto}.md\:bui-col-start-1{grid-column-start:1}.md\:bui-col-start-2{grid-column-start:2}.md\:bui-col-start-3{grid-column-start:3}.md\:bui-col-start-4{grid-column-start:4}.md\:bui-col-start-5{grid-column-start:5}.md\:bui-col-start-6{grid-column-start:6}.md\:bui-col-start-7{grid-column-start:7}.md\:bui-col-start-8{grid-column-start:8}.md\:bui-col-start-9{grid-column-start:9}.md\:bui-col-start-10{grid-column-start:10}.md\:bui-col-start-11{grid-column-start:11}.md\:bui-col-start-12{grid-column-start:12}.md\:bui-col-start-13{grid-column-start:13}.md\:bui-col-start-auto{grid-column-start:auto}.md\:bui-col-end-1{grid-column-end:1}.md\:bui-col-end-2{grid-column-end:2}.md\:bui-col-end-3{grid-column-end:3}.md\:bui-col-end-4{grid-column-end:4}.md\:bui-col-end-5{grid-column-end:5}.md\:bui-col-end-6{grid-column-end:6}.md\:bui-col-end-7{grid-column-end:7}.md\:bui-col-end-8{grid-column-end:8}.md\:bui-col-end-9{grid-column-end:9}.md\:bui-col-end-10{grid-column-end:10}.md\:bui-col-end-11{grid-column-end:11}.md\:bui-col-end-12{grid-column-end:12}.md\:bui-col-end-13{grid-column-end:13}.md\:bui-col-end-auto{grid-column-end:auto}.md\:bui-row-span-1{grid-row:span 1/span 1}.md\:bui-row-span-2{grid-row:span 2/span 2}.md\:bui-row-span-3{grid-row:span 3/span 3}.md\:bui-row-span-4{grid-row:span 4/span 4}.md\:bui-row-span-5{grid-row:span 5/span 5}.md\:bui-row-span-6{grid-row:span 6/span 6}.md\:bui-row-span-7{grid-row:span 7/span 7}.md\:bui-row-span-8{grid-row:span 8/span 8}.md\:bui-row-span-9{grid-row:span 9/span 9}.md\:bui-row-span-10{grid-row:span 10/span 10}.md\:bui-row-span-11{grid-row:span 11/span 11}.md\:bui-row-span-12{grid-row:span 12/span 12}.md\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1280px){.lg\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.lg\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.lg\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.lg\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.lg\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.lg\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.lg\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.lg\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.lg\:bui-col-span-1{grid-column:span 1/span 1}.lg\:bui-col-span-2{grid-column:span 2/span 2}.lg\:bui-col-span-3{grid-column:span 3/span 3}.lg\:bui-col-span-4{grid-column:span 4/span 4}.lg\:bui-col-span-5{grid-column:span 5/span 5}.lg\:bui-col-span-6{grid-column:span 6/span 6}.lg\:bui-col-span-7{grid-column:span 7/span 7}.lg\:bui-col-span-8{grid-column:span 8/span 8}.lg\:bui-col-span-9{grid-column:span 9/span 9}.lg\:bui-col-span-10{grid-column:span 10/span 10}.lg\:bui-col-span-11{grid-column:span 11/span 11}.lg\:bui-col-span-12{grid-column:span 12/span 12}.lg\:bui-col-span-auto{grid-column:span auto/span auto}.lg\:bui-col-start-1{grid-column-start:1}.lg\:bui-col-start-2{grid-column-start:2}.lg\:bui-col-start-3{grid-column-start:3}.lg\:bui-col-start-4{grid-column-start:4}.lg\:bui-col-start-5{grid-column-start:5}.lg\:bui-col-start-6{grid-column-start:6}.lg\:bui-col-start-7{grid-column-start:7}.lg\:bui-col-start-8{grid-column-start:8}.lg\:bui-col-start-9{grid-column-start:9}.lg\:bui-col-start-10{grid-column-start:10}.lg\:bui-col-start-11{grid-column-start:11}.lg\:bui-col-start-12{grid-column-start:12}.lg\:bui-col-start-13{grid-column-start:13}.lg\:bui-col-start-auto{grid-column-start:auto}.lg\:bui-col-end-1{grid-column-end:1}.lg\:bui-col-end-2{grid-column-end:2}.lg\:bui-col-end-3{grid-column-end:3}.lg\:bui-col-end-4{grid-column-end:4}.lg\:bui-col-end-5{grid-column-end:5}.lg\:bui-col-end-6{grid-column-end:6}.lg\:bui-col-end-7{grid-column-end:7}.lg\:bui-col-end-8{grid-column-end:8}.lg\:bui-col-end-9{grid-column-end:9}.lg\:bui-col-end-10{grid-column-end:10}.lg\:bui-col-end-11{grid-column-end:11}.lg\:bui-col-end-12{grid-column-end:12}.lg\:bui-col-end-13{grid-column-end:13}.lg\:bui-col-end-auto{grid-column-end:auto}.lg\:bui-row-span-1{grid-row:span 1/span 1}.lg\:bui-row-span-2{grid-row:span 2/span 2}.lg\:bui-row-span-3{grid-row:span 3/span 3}.lg\:bui-row-span-4{grid-row:span 4/span 4}.lg\:bui-row-span-5{grid-row:span 5/span 5}.lg\:bui-row-span-6{grid-row:span 6/span 6}.lg\:bui-row-span-7{grid-row:span 7/span 7}.lg\:bui-row-span-8{grid-row:span 8/span 8}.lg\:bui-row-span-9{grid-row:span 9/span 9}.lg\:bui-row-span-10{grid-row:span 10/span 10}.lg\:bui-row-span-11{grid-row:span 11/span 11}.lg\:bui-row-span-12{grid-row:span 12/span 12}.lg\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1536px){.xl\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xl\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xl\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xl\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xl\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xl\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xl\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.xl\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.xl\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.xl\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.xl\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xl\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.xl\:bui-col-span-1{grid-column:span 1/span 1}.xl\:bui-col-span-2{grid-column:span 2/span 2}.xl\:bui-col-span-3{grid-column:span 3/span 3}.xl\:bui-col-span-4{grid-column:span 4/span 4}.xl\:bui-col-span-5{grid-column:span 5/span 5}.xl\:bui-col-span-6{grid-column:span 6/span 6}.xl\:bui-col-span-7{grid-column:span 7/span 7}.xl\:bui-col-span-8{grid-column:span 8/span 8}.xl\:bui-col-span-9{grid-column:span 9/span 9}.xl\:bui-col-span-10{grid-column:span 10/span 10}.xl\:bui-col-span-11{grid-column:span 11/span 11}.xl\:bui-col-span-12{grid-column:span 12/span 12}.xl\:bui-col-span-auto{grid-column:span auto/span auto}.xl\:bui-col-start-1{grid-column-start:1}.xl\:bui-col-start-2{grid-column-start:2}.xl\:bui-col-start-3{grid-column-start:3}.xl\:bui-col-start-4{grid-column-start:4}.xl\:bui-col-start-5{grid-column-start:5}.xl\:bui-col-start-6{grid-column-start:6}.xl\:bui-col-start-7{grid-column-start:7}.xl\:bui-col-start-8{grid-column-start:8}.xl\:bui-col-start-9{grid-column-start:9}.xl\:bui-col-start-10{grid-column-start:10}.xl\:bui-col-start-11{grid-column-start:11}.xl\:bui-col-start-12{grid-column-start:12}.xl\:bui-col-start-13{grid-column-start:13}.xl\:bui-col-start-auto{grid-column-start:auto}.xl\:bui-col-end-1{grid-column-end:1}.xl\:bui-col-end-2{grid-column-end:2}.xl\:bui-col-end-3{grid-column-end:3}.xl\:bui-col-end-4{grid-column-end:4}.xl\:bui-col-end-5{grid-column-end:5}.xl\:bui-col-end-6{grid-column-end:6}.xl\:bui-col-end-7{grid-column-end:7}.xl\:bui-col-end-8{grid-column-end:8}.xl\:bui-col-end-9{grid-column-end:9}.xl\:bui-col-end-10{grid-column-end:10}.xl\:bui-col-end-11{grid-column-end:11}.xl\:bui-col-end-12{grid-column-end:12}.xl\:bui-col-end-13{grid-column-end:13}.xl\:bui-col-end-auto{grid-column-end:auto}.xl\:bui-row-span-1{grid-row:span 1/span 1}.xl\:bui-row-span-2{grid-row:span 2/span 2}.xl\:bui-row-span-3{grid-row:span 3/span 3}.xl\:bui-row-span-4{grid-row:span 4/span 4}.xl\:bui-row-span-5{grid-row:span 5/span 5}.xl\:bui-row-span-6{grid-row:span 6/span 6}.xl\:bui-row-span-7{grid-row:span 7/span 7}.xl\:bui-row-span-8{grid-row:span 8/span 8}.xl\:bui-row-span-9{grid-row:span 9/span 9}.xl\:bui-row-span-10{grid-row:span 10/span 10}.xl\:bui-row-span-11{grid-row:span 11/span 11}.xl\:bui-row-span-12{grid-row:span 12/span 12}.xl\:bui-row-span-auto{grid-row:span auto/span auto}}.bui-gap{gap:var(--gap)}.bui-gap-0\.5{gap:var(--bui-space-0_5)}.bui-gap-1{gap:var(--bui-space-1)}.bui-gap-1\.5{gap:var(--bui-space-1_5)}.bui-gap-2{gap:var(--bui-space-2)}.bui-gap-3{gap:var(--bui-space-3)}.bui-gap-4{gap:var(--bui-space-4)}.bui-gap-5{gap:var(--bui-space-5)}.bui-gap-6{gap:var(--bui-space-6)}.bui-gap-7{gap:var(--bui-space-7)}.bui-gap-8{gap:var(--bui-space-8)}.bui-gap-9{gap:var(--bui-space-9)}.bui-gap-10{gap:var(--bui-space-10)}.bui-gap-11{gap:var(--bui-space-11)}.bui-gap-12{gap:var(--bui-space-12)}.bui-gap-13{gap:var(--bui-space-13)}.bui-gap-14{gap:var(--bui-space-14)}@media (width>=640px){.xs\:bui-gap{gap:var(--gap-xs)}.xs\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.xs\:bui-gap-1{gap:var(--bui-space-1)}.xs\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.xs\:bui-gap-2{gap:var(--bui-space-2)}.xs\:bui-gap-3{gap:var(--bui-space-3)}.xs\:bui-gap-4{gap:var(--bui-space-4)}.xs\:bui-gap-5{gap:var(--bui-space-5)}.xs\:bui-gap-6{gap:var(--bui-space-6)}.xs\:bui-gap-7{gap:var(--bui-space-7)}.xs\:bui-gap-8{gap:var(--bui-space-8)}.xs\:bui-gap-9{gap:var(--bui-space-9)}.xs\:bui-gap-10{gap:var(--bui-space-10)}.xs\:bui-gap-11{gap:var(--bui-space-11)}.xs\:bui-gap-12{gap:var(--bui-space-12)}.xs\:bui-gap-13{gap:var(--bui-space-13)}.xs\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-gap{gap:var(--gap-sm)}.sm\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.sm\:bui-gap-1{gap:var(--bui-space-1)}.sm\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.sm\:bui-gap-2{gap:var(--bui-space-2)}.sm\:bui-gap-3{gap:var(--bui-space-3)}.sm\:bui-gap-4{gap:var(--bui-space-4)}.sm\:bui-gap-5{gap:var(--bui-space-5)}.sm\:bui-gap-6{gap:var(--bui-space-6)}.sm\:bui-gap-7{gap:var(--bui-space-7)}.sm\:bui-gap-8{gap:var(--bui-space-8)}.sm\:bui-gap-9{gap:var(--bui-space-9)}.sm\:bui-gap-10{gap:var(--bui-space-10)}.sm\:bui-gap-11{gap:var(--bui-space-11)}.sm\:bui-gap-12{gap:var(--bui-space-12)}.sm\:bui-gap-13{gap:var(--bui-space-13)}.sm\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-gap{gap:var(--gap-md)}.md\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.md\:bui-gap-1{gap:var(--bui-space-1)}.md\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.md\:bui-gap-2{gap:var(--bui-space-2)}.md\:bui-gap-3{gap:var(--bui-space-3)}.md\:bui-gap-4{gap:var(--bui-space-4)}.md\:bui-gap-5{gap:var(--bui-space-5)}.md\:bui-gap-6{gap:var(--bui-space-6)}.md\:bui-gap-7{gap:var(--bui-space-7)}.md\:bui-gap-8{gap:var(--bui-space-8)}.md\:bui-gap-9{gap:var(--bui-space-9)}.md\:bui-gap-10{gap:var(--bui-space-10)}.md\:bui-gap-11{gap:var(--bui-space-11)}.md\:bui-gap-12{gap:var(--bui-space-12)}.md\:bui-gap-13{gap:var(--bui-space-13)}.md\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-gap{gap:var(--gap-lg)}.lg\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.lg\:bui-gap-1{gap:var(--bui-space-1)}.lg\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.lg\:bui-gap-2{gap:var(--bui-space-2)}.lg\:bui-gap-3{gap:var(--bui-space-3)}.lg\:bui-gap-4{gap:var(--bui-space-4)}.lg\:bui-gap-5{gap:var(--bui-space-5)}.lg\:bui-gap-6{gap:var(--bui-space-6)}.lg\:bui-gap-7{gap:var(--bui-space-7)}.lg\:bui-gap-8{gap:var(--bui-space-8)}.lg\:bui-gap-9{gap:var(--bui-space-9)}.lg\:bui-gap-10{gap:var(--bui-space-10)}.lg\:bui-gap-11{gap:var(--bui-space-11)}.lg\:bui-gap-12{gap:var(--bui-space-12)}.lg\:bui-gap-13{gap:var(--bui-space-13)}.lg\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-gap{gap:var(--gap-xl)}.xl\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.xl\:bui-gap-1{gap:var(--bui-space-1)}.xl\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.xl\:bui-gap-2{gap:var(--bui-space-2)}.xl\:bui-gap-3{gap:var(--bui-space-3)}.xl\:bui-gap-4{gap:var(--bui-space-4)}.xl\:bui-gap-5{gap:var(--bui-space-5)}.xl\:bui-gap-6{gap:var(--bui-space-6)}.xl\:bui-gap-7{gap:var(--bui-space-7)}.xl\:bui-gap-8{gap:var(--bui-space-8)}.xl\:bui-gap-9{gap:var(--bui-space-9)}.xl\:bui-gap-10{gap:var(--bui-space-10)}.xl\:bui-gap-11{gap:var(--bui-space-11)}.xl\:bui-gap-12{gap:var(--bui-space-12)}.xl\:bui-gap-13{gap:var(--bui-space-13)}.xl\:bui-gap-14{gap:var(--bui-space-14)}}.bui-align-start{align-items:start}.bui-align-center{align-items:center}.bui-align-end{align-items:end}.bui-align-baseline{align-items:baseline}.bui-align-stretch{align-items:stretch}.bui-jc-start{justify-content:start}.bui-jc-center{justify-content:center}.bui-jc-end{justify-content:end}.bui-jc-between{justify-content:space-between}.bui-fd-row{flex-direction:row}.bui-fd-column{flex-direction:column}.bui-fd-row-reverse{flex-direction:row-reverse}.bui-fd-column-reverse{flex-direction:column-reverse}@media (width>=640px){.xs\:bui-align-start{align-items:start}.xs\:bui-align-center{align-items:center}.xs\:bui-align-end{align-items:end}.xs\:bui-align-stretch{align-items:stretch}.xs\:bui-jc-start{justify-content:start}.xs\:bui-jc-center{justify-content:center}.xs\:bui-jc-end{justify-content:end}.xs\:bui-jc-between{justify-content:space-between}.xs\:bui-fd-row{flex-direction:row}.xs\:bui-fd-column{flex-direction:column}.xs\:bui-fd-row-reverse{flex-direction:row-reverse}.xs\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=768px){.sm\:bui-align-start{align-items:start}.sm\:bui-align-center{align-items:center}.sm\:bui-align-end{align-items:end}.sm\:bui-align-stretch{align-items:stretch}.sm\:bui-jc-start{justify-content:start}.sm\:bui-jc-center{justify-content:center}.sm\:bui-jc-end{justify-content:end}.sm\:bui-jc-between{justify-content:space-between}.sm\:bui-fd-row{flex-direction:row}.sm\:bui-fd-column{flex-direction:column}.sm\:bui-fd-row-reverse{flex-direction:row-reverse}.sm\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1024px){.md\:bui-align-start{align-items:start}.md\:bui-align-center{align-items:center}.md\:bui-align-end{align-items:end}.md\:bui-align-stretch{align-items:stretch}.md\:bui-jc-start{justify-content:start}.md\:bui-jc-center{justify-content:center}.md\:bui-jc-end{justify-content:end}.md\:bui-jc-between{justify-content:space-between}.md\:bui-fd-row{flex-direction:row}.md\:bui-fd-column{flex-direction:column}.md\:bui-fd-row-reverse{flex-direction:row-reverse}.md\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1280px){.lg\:bui-align-start{align-items:start}.lg\:bui-align-center{align-items:center}.lg\:bui-align-end{align-items:end}.lg\:bui-align-stretch{align-items:stretch}.lg\:bui-jc-start{justify-content:start}.lg\:bui-jc-center{justify-content:center}.lg\:bui-jc-end{justify-content:end}.lg\:bui-jc-between{justify-content:space-between}.lg\:bui-fd-row{flex-direction:row}.lg\:bui-fd-column{flex-direction:column}.lg\:bui-fd-row-reverse{flex-direction:row-reverse}.lg\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1536px){.xl\:bui-align-start{align-items:start}.xl\:bui-align-center{align-items:center}.xl\:bui-align-end{align-items:end}.xl\:bui-align-stretch{align-items:stretch}.xl\:bui-jc-start{justify-content:start}.xl\:bui-jc-center{justify-content:center}.xl\:bui-jc-end{justify-content:end}.xl\:bui-jc-between{justify-content:space-between}.xl\:bui-fd-row{flex-direction:row}.xl\:bui-fd-column{flex-direction:column}.xl\:bui-fd-row-reverse{flex-direction:row-reverse}.xl\:bui-fd-column-reverse{flex-direction:column-reverse}}} \ No newline at end of file diff --git a/docs-ui/public/theme-spotify.css b/docs-ui/public/theme-spotify.css index b0bf517701..0f2ecc6081 100644 --- a/docs-ui/public/theme-spotify.css +++ b/docs-ui/public/theme-spotify.css @@ -1 +1 @@ -@font-face{font-family:CircularSpTitle;font-weight:900;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSpTitle-Black-4588c99025b967475c31695b0743dd1a.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSpTitle-Black-506746f387a26f25aa3d023b3e501d34.woff)format("woff")}@font-face{font-family:CircularSpTitle;font-weight:700;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSpTitle-Bold-b2586b06a2e1522e9d879d84c2792a58.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSpTitle-Bold-1624fb2df28c20d7203d7fb86ce8b481.woff)format("woff")}@font-face{font-family:CircularSp;font-weight:700;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSp-Bold-602e7aefc706aa36c6ec1324b9bbc461.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSp-Bold-856afe2da4ba4e61239b129e2c16d633.woff)format("woff")}@font-face{font-family:CircularSp;font-weight:400;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSp-Book-a00e99ef9996a3a157fb6b746856d04f.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSp-Book-3f73da7d35bd81c706bce7bbb84964de.woff)format("woff")}@font-face{font-family:CircularSp;font-weight:700;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSp-Bold-602e7aefc706aa36c6ec1324b9bbc461.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSp-Bold-856afe2da4ba4e61239b129e2c16d633.woff)format("woff")}[data-theme-name=spotify]{--bui-font-text:CircularSp,CircularSp-Arab,CircularSp-Hebr,CircularSp-Cyrl,CircularSp-Grek,CircularSp-Deva;--bui-font-title:CircularSpTitle,CircularSp-Arab,CircularSp-Hebr,CircularSp-Cyrl,CircularSp-Grek,CircularSp-Deva;--bui-font-regular:CircularSp,CircularSp-Arab,CircularSp-Hebr,CircularSp-Cyrl,CircularSp-Grek,CircularSp-Deva;& .bui-Button{border-radius:var(--bui-radius-3);padding-inline:var(--bui-space-3)}& .bui-ButtonIcon{padding:0}& .bui-MenuPopup{box-sizing:border-box;max-width:21.25rem}& .bui-MenuSubmenuTrigger,& .bui-MenuItem{height:auto;min-height:2rem}& .bui-Text{font-family:var(--bui-font-text)}& .bui-Heading{font-family:var(--bui-font-title)}& .bui-TableRow{border-radius:var(--bui-radius-4);border:none}& .bui-TableRow:hover td:first-child{border-top-left-radius:var(--bui-radius-2);border-bottom-left-radius:var(--bui-radius-2)}& .bui-TableRow:hover td:last-child{border-top-right-radius:var(--bui-radius-2);border-bottom-right-radius:var(--bui-radius-2)}& .bui-TableBody:before,& .bui-TableBody:after{line-height:var(--bui-space-1);content:"‌";display:block}& .bui-TableHeader .bui-TableRow{border-bottom:1px solid var(--bui-border)}& .bui-TableHead{font-size:var(--bui-font-size-2);color:var(--bui-fg-secondary);font-weight:var(--bui-font-weight-regular)}& .bui-HeaderToolbar{padding-top:var(--bui-space-2);padding-inline:var(--bui-space-2)}& .bui-HeaderToolbarWrapper{border-radius:var(--bui-radius-3);padding-inline:var(--bui-space-3);border:none}& .bui-HeaderToolbarControls{right:calc(var(--bui-space-3) - 1px)}& .bui-HeaderTabsWrapper{margin-top:var(--bui-space-2);margin-inline:var(--bui-space-2);border-radius:var(--bui-radius-3);padding-inline:var(--bui-space-1);border:none}& .bui-Input{border-radius:var(--bui-radius-3)}& .bui-Tag{border-radius:var(--bui-radius-full)}}[data-theme-mode=light][data-theme-name=spotify]{--bui-bg:var(--bui-gray-1);--bui-bg-surface-1:var(--bui-white);--bui-bg-surface-2:var(--bui-gray-2);--bui-bg-solid:#1ed760;--bui-bg-solid-hover:#3be477;--bui-bg-solid-pressed:#1abc54;--bui-bg-solid-disabled:var(--bui-gray-2);--bui-fg-primary:var(--bui-black);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-solid:var(--bui-black);--bui-border:var(--bui-gray-3);--bui-border-hover:#0000004d;--bui-border-pressed:#00000080;--bui-border-disabled:#0000001a;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#0003;& .bui-HeaderToolbarWrapper,& .bui-HeaderTabsWrapper{border:1px solid var(--bui-border)}}[data-theme-mode=dark][data-theme-name=spotify]{--bui-bg:var(--bui-black);--bui-bg-surface-1:var(--bui-gray-1);--bui-bg-surface-2:var(--bui-gray-2);--bui-bg-solid:#1ed760;--bui-bg-solid-hover:#3be477;--bui-bg-solid-pressed:#1abc54;--bui-bg-solid-disabled:#0f6c30;--bui-bg-tint:#242424;--bui-bg-tint-hover:#202020;--bui-bg-tint-pressed:#292929;--bui-bg-tint-disabled:#fffc;--bui-bg-danger:#3b1219;--bui-bg-warning:#302008;--bui-bg-success:#132d21;--bui-fg-primary:var(--bui-white);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:var(--bui-white);--bui-fg-link-hover:var(--bui-white);--bui-fg-disabled:var(--bui-gray-5);--bui-fg-solid:var(--bui-black);--bui-fg-solid-disabled:#083618;--bui-fg-tint:var(--bui-white);--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#e22b2b;--bui-fg-warning:#e36d05;--bui-fg-success:#1db954;--bui-border:var(--bui-gray-3);--bui-border-hover:#fff6;--bui-border-pressed:#ffffff80;--bui-border-disabled:#fff3;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#fff3} \ No newline at end of file +@font-face{font-family:CircularSpTitle;font-weight:900;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSpTitle-Black-4588c99025b967475c31695b0743dd1a.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSpTitle-Black-506746f387a26f25aa3d023b3e501d34.woff)format("woff")}@font-face{font-family:CircularSpTitle;font-weight:700;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSpTitle-Bold-b2586b06a2e1522e9d879d84c2792a58.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSpTitle-Bold-1624fb2df28c20d7203d7fb86ce8b481.woff)format("woff")}@font-face{font-family:CircularSp;font-weight:700;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSp-Bold-602e7aefc706aa36c6ec1324b9bbc461.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSp-Bold-856afe2da4ba4e61239b129e2c16d633.woff)format("woff")}@font-face{font-family:CircularSp;font-weight:400;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSp-Book-a00e99ef9996a3a157fb6b746856d04f.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSp-Book-3f73da7d35bd81c706bce7bbb84964de.woff)format("woff")}@font-face{font-family:CircularSp;font-weight:700;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSp-Bold-602e7aefc706aa36c6ec1324b9bbc461.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSp-Bold-856afe2da4ba4e61239b129e2c16d633.woff)format("woff")}[data-theme-name=spotify]{--bui-font-text:CircularSp,CircularSp-Arab,CircularSp-Hebr,CircularSp-Cyrl,CircularSp-Grek,CircularSp-Deva;--bui-font-title:CircularSpTitle,CircularSp-Arab,CircularSp-Hebr,CircularSp-Cyrl,CircularSp-Grek,CircularSp-Deva;--bui-font-regular:CircularSp,CircularSp-Arab,CircularSp-Hebr,CircularSp-Cyrl,CircularSp-Grek,CircularSp-Deva;& .bui-Button{border-radius:var(--bui-radius-3);padding-inline:var(--bui-space-3)}& .bui-ButtonIcon{padding:0}& .bui-MenuPopup{box-sizing:border-box;max-width:21.25rem}& .bui-MenuSubmenuTrigger,& .bui-MenuItem{height:auto;min-height:2rem}& .bui-Text{font-family:var(--bui-font-text)}& .bui-Heading{font-family:var(--bui-font-title)}& .bui-TableRow{border-radius:var(--bui-radius-4);border:none}& .bui-TableRow:hover td:first-child{border-top-left-radius:var(--bui-radius-2);border-bottom-left-radius:var(--bui-radius-2)}& .bui-TableRow:hover td:last-child{border-top-right-radius:var(--bui-radius-2);border-bottom-right-radius:var(--bui-radius-2)}& .bui-TableBody:before,& .bui-TableBody:after{line-height:var(--bui-space-1);content:"‌";display:block}& .bui-TableHeader .bui-TableRow{border-bottom:1px solid var(--bui-border)}& .bui-TableHead{font-size:var(--bui-font-size-2);color:var(--bui-fg-secondary);font-weight:var(--bui-font-weight-regular)}& .bui-HeaderToolbar{padding-top:var(--bui-space-2);padding-inline:var(--bui-space-2)}& .bui-HeaderToolbarWrapper{border-radius:var(--bui-radius-3);padding-inline:var(--bui-space-3);border:none}& .bui-HeaderToolbarControls{right:calc(var(--bui-space-3) - 1px)}& .bui-HeaderTabsWrapper{margin-top:var(--bui-space-2);margin-inline:var(--bui-space-2);border-radius:var(--bui-radius-3);padding-inline:var(--bui-space-1);border:none}& .bui-Input{border-radius:var(--bui-radius-3)}& .bui-Tag{border-radius:var(--bui-radius-full)}}[data-theme-mode=light][data-theme-name=spotify]{--bui-bg-solid:#1ed760;--bui-bg-solid-hover:#3be477;--bui-bg-solid-pressed:#1abc54;--bui-bg-solid-disabled:var(--bui-gray-2);--bui-fg-primary:var(--bui-black);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-solid:var(--bui-black);--bui-border:var(--bui-gray-3);--bui-border-hover:#0000004d;--bui-border-pressed:#00000080;--bui-border-disabled:#0000001a;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#0003;& .bui-HeaderToolbarWrapper,& .bui-HeaderTabsWrapper{border:1px solid var(--bui-border)}}[data-theme-mode=dark][data-theme-name=spotify]{--bui-bg-surface-0:var(--bui-black);--bui-bg-solid:#1ed760;--bui-bg-solid-hover:#3be477;--bui-bg-solid-pressed:#1abc54;--bui-bg-solid-disabled:#0f6c30;--bui-bg-tint:#242424;--bui-bg-tint-hover:#202020;--bui-bg-tint-pressed:#292929;--bui-bg-tint-disabled:#fffc;--bui-bg-danger:#3b1219;--bui-bg-warning:#302008;--bui-bg-success:#132d21;--bui-fg-primary:var(--bui-white);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:var(--bui-white);--bui-fg-link-hover:var(--bui-white);--bui-fg-disabled:var(--bui-gray-5);--bui-fg-solid:var(--bui-black);--bui-fg-solid-disabled:#083618;--bui-fg-tint:var(--bui-white);--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#e22b2b;--bui-fg-warning:#e36d05;--bui-fg-success:#1db954;--bui-border:var(--bui-gray-3);--bui-border-hover:#fff6;--bui-border-pressed:#ffffff80;--bui-border-disabled:#fff3;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#fff3} \ No newline at end of file diff --git a/docs-ui/src/app/tokens/page.mdx b/docs-ui/src/app/tokens/page.mdx index 8e9031c81c..97266afbf4 100644 --- a/docs-ui/src/app/tokens/page.mdx +++ b/docs-ui/src/app/tokens/page.mdx @@ -187,7 +187,7 @@ These colors are used for special purposes like ring, scrollbar, ... ## Core background colors These colors are used for the background of your application. We are mostly using for now a -single elevated background for panels. `--bui-bg` should mostly use as the main background +single elevated background for panels. `--bui-bg-surface-0` should mostly use as the main background color of your app. @@ -200,7 +200,7 @@ color of your app. - --bui-bg + --bui-bg-surface-0 The background color of your Backstage instance. diff --git a/docs-ui/src/components/Snippet/styles.module.css b/docs-ui/src/components/Snippet/styles.module.css index 3141564293..ed5bd22def 100644 --- a/docs-ui/src/components/Snippet/styles.module.css +++ b/docs-ui/src/components/Snippet/styles.module.css @@ -7,7 +7,7 @@ .preview { border-radius: 8px; box-shadow: inset 0 0 0 1px var(--border); - background-color: var(--bui-bg); + background-color: var(--bui-bg-surface-0); padding: 1px; position: relative; } diff --git a/docs-ui/src/content/table.mdx b/docs-ui/src/content/table.mdx index d1a190e9dd..00b7290532 100644 --- a/docs-ui/src/content/table.mdx +++ b/docs-ui/src/content/table.mdx @@ -13,7 +13,9 @@ import { tableHybridSnippet, tableCellInteractionsSnippet, tablePaginationSnippet, - tableSelectionSnippet, + tableSelectionActionsSnippet, + tableSelectionModeSnippet, + tableSelectionBehaviorSnippet, tableSortingSnippet, columnPropDefs, rowPropDefs, @@ -91,7 +93,36 @@ Coming soon. ### Row selection -Coming soon. +Tables support row selection with two configuration options: selection mode and selection behavior. + +#### Selection mode + +Use `selectionMode` to control how many rows can be selected. With `single`, only one row can be selected at a time. With `multiple`, any number of rows can be selected, and a header checkbox provides select-all functionality. + +} + 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 diff --git a/docs-ui/src/content/table.props.ts b/docs-ui/src/content/table.props.ts index 443526182e..21bd5597c6 100644 --- a/docs-ui/src/content/table.props.ts +++ b/docs-ui/src/content/table.props.ts @@ -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/snippets/code-snippets.ts b/docs-ui/src/snippets/code-snippets.ts index 1a22a24340..57169ae32a 100644 --- a/docs-ui/src/snippets/code-snippets.ts +++ b/docs-ui/src/snippets/code-snippets.ts @@ -4,7 +4,7 @@ export const customTheme = `:root { --bui-font-regular: system-ui; --bui-font-weight-regular: 400; --bui-font-weight-bold: 600; - --bui-bg: #f8f8f8; + --bui-bg-surface-0: #f8f8f8; --bui-bg-surface-1: #fff; /* ... other CSS variables */ @@ -19,7 +19,7 @@ export const customTheme = `:root { --bui-font-regular: system-ui; --bui-font-weight-regular: 400; --bui-font-weight-bold: 600; - --bui-bg: #f8f8f8; + --bui-bg-surface-0: #f8f8f8; --bui-bg-surface-1: #fff; /* ... other CSS variables */ diff --git a/docs-ui/src/utils/changelog.ts b/docs-ui/src/utils/changelog.ts index 1179d7e189..0c3286d051 100644 --- a/docs-ui/src/utils/changelog.ts +++ b/docs-ui/src/utils/changelog.ts @@ -1,4 +1,5 @@ export * from './types'; +import { changelog_0_9_0 } from './changelogs/v0.9.0'; import { changelog_0_8_2 } from './changelogs/v0.8.2'; import { changelog_0_8_0 } from './changelogs/v0.8.0'; import { changelog_0_7_2 } from './changelogs/v0.7.2'; @@ -14,6 +15,7 @@ import { changelog_0_2_0 } from './changelogs/v0.2.0'; import { changelog_0_1_0 } from './changelogs/v0.1.0'; export const changelog = [ + ...changelog_0_9_0, ...changelog_0_8_2, ...changelog_0_8_0, ...changelog_0_7_2, 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/yarn.lock b/docs-ui/yarn.lock index a9e9b97305..755b88ec09 100644 --- a/docs-ui/yarn.lock +++ b/docs-ui/yarn.lock @@ -31,11 +31,11 @@ __metadata: linkType: hard "@base-ui-components/react@npm:^1.0.0-beta.4": - version: 1.0.0-beta.6 - resolution: "@base-ui-components/react@npm:1.0.0-beta.6" + 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.0" + "@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" @@ -48,13 +48,13 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10/b3cfa5449f596c6cb509017a33c31fad531c10ee3dc70ee98ca5a48dfc02a8333012bd29df660833f98b73ee895f34ff51037eeef35b58111492d98454881eee + checksum: 10/ea22dd8206962a1b74fea30f4b20f8b5995bd20155466a5ee7fc7a59cc1632e2ba9615094eac9983dbf8387e50daa8b078024da85b3a8d60cdbc43fb1cfc3b50 languageName: node linkType: hard -"@base-ui-components/utils@npm:0.2.0": - version: 0.2.0 - resolution: "@base-ui-components/utils@npm:0.2.0" +"@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" @@ -67,7 +67,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10/b5eae460e3f45fdb2c5116c94612c5b0c00190d4f0763836ba121e36e5c46c1caad862f4aa05827d90a8ebe924c29e42a7561c124de23ce3dc565dba2a6dff98 + checksum: 10/a67803b5637cfce1071963f958306f90784f7934ae0b072b450dbf7f2e3423ea9b40e420672849cfcf2965c94ac5960d2c6a5e04e359f1f76166b929dda932a2 languageName: node linkType: hard @@ -179,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.6 - resolution: "@codemirror/view@npm:6.38.6" + 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/5a047337a98de111817ce8c8d39e6429c90ca0b0a4d2678d6e161e9e5961b1d476a891f447ab7a05cac395d4a93530e7c68bedd93191285265f0742a308ad00b + checksum: 10/81b1508015a378e4719d0239254173f0c5cd340c2abf96eb488fe5fb474bdb37ec1f010b9890ced774accd7aeb9443e7337cb6a89544b954273e5ddabece7cea languageName: node linkType: hard @@ -854,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.9": + version: 15.5.9 + resolution: "@next/env@npm:15.5.9" + checksum: 10/962329701343c2617c85ae99ef35adfd9e8f7b58d9c0316f2f0857f82875a91ef3151ef0743c50964b0066aa3d1214c5193cf229f7757d7c9329f7fb95605a4a 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: @@ -883,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 @@ -1153,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 @@ -1240,11 +1240,11 @@ __metadata: languageName: node linkType: hard -"@storybook/core@npm:8.6.14": - version: 8.6.14 - resolution: "@storybook/core@npm:8.6.14" +"@storybook/core@npm:8.6.15": + version: 8.6.15 + resolution: "@storybook/core@npm:8.6.15" dependencies: - "@storybook/theming": "npm:8.6.14" + "@storybook/theming": "npm:8.6.15" better-opn: "npm:^3.0.2" browser-assert: "npm:^1.2.1" esbuild: "npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0" @@ -1260,7 +1260,7 @@ __metadata: peerDependenciesMeta: prettier: optional: true - checksum: 10/8f8c811edd4ea8dcedcc63a79b3168dc83aa0401e3760990cb5995ea6beaac4026c6ccc1046182f41eed7c09333005f3348d8b42f89fc5dd5b2ce011a9d2a48f + checksum: 10/d268d6fa00c38b35e5c363ee33779c2e087ab8e4681e0e205baa2fdb2780ea9feda3c9f6db35d60092778878d2782b2093c744bdf1af173c5688c3e1e0e960ac languageName: node linkType: hard @@ -1334,6 +1334,15 @@ __metadata: languageName: node linkType: hard +"@storybook/theming@npm:8.6.15": + version: 8.6.15 + resolution: "@storybook/theming@npm:8.6.15" + peerDependencies: + storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 + checksum: 10/f02760831a13d7af9dbfeb6feea949f4c13c897861cbc75253a6776d133891567889c8d99c7e91a99124d0772c3bde1f978984c83b19117d1d7c908ed7eb8409 + languageName: node + linkType: hard + "@swc/helpers@npm:0.5.15": version: 0.5.15 resolution: "@swc/helpers@npm:0.5.15" @@ -1416,12 +1425,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^20": - version: 20.19.11 - resolution: "@types/node@npm:20.19.11" +"@types/node@npm:^22.13.14": + version: 22.19.2 + resolution: "@types/node@npm:22.19.2" dependencies: undici-types: "npm:~6.21.0" - checksum: 10/7179ed464a781cad20117362df58c12839b40922b53356e1a74f86c99d9d1ef9a27edce360901608f896c247c9c4131ba9c7222331e92cde17b723c75d5c8f2e + checksum: 10/c7e72fde4aef120d984416f4aa01d23b0704eeed7a0a63cdf6178783361f3985abb02a65133b80f47226ad61f38a27fe82c3cd6dd6a62d5b82bc643fe54b9c97 languageName: node linkType: hard @@ -1618,8 +1627,8 @@ __metadata: linkType: hard "@uiw/codemirror-themes@npm:^4.23.7": - version: 4.25.1 - resolution: "@uiw/codemirror-themes@npm:4.25.1" + version: 4.25.4 + resolution: "@uiw/codemirror-themes@npm:4.25.4" dependencies: "@codemirror/language": "npm:^6.0.0" "@codemirror/state": "npm:^6.0.0" @@ -1628,7 +1637,7 @@ __metadata: "@codemirror/language": ">=6.0.0" "@codemirror/state": ">=6.0.0" "@codemirror/view": ">=6.0.0" - checksum: 10/337316f7c15a57bca4824498769448e38861197ab3d73b3bc58b2193028751c525a175af06f1f3984fd25b001f990e1d30ce442ddc756642698d9f8adff7f95a + checksum: 10/aab62e2c16a10dbde4592cef704d386c1a9e3caece4ff0eed85e9d9a5acdeaf3fd8717e2cb6a6795e12e890b8600956effb52d453aacbc618934e7ae8c3e59e3 languageName: node linkType: hard @@ -2188,9 +2197,9 @@ __metadata: linkType: hard "caniuse-lite@npm:^1.0.30001579": - version: 1.0.30001735 - resolution: "caniuse-lite@npm:1.0.30001735" - checksum: 10/4b404ed363a5ccf3ec07144c2360106aa47dcec1f896ea6705a2773c6977e9ffb19a2b58be9492462733170e29b4823211533b0986d5a25f0f65f7ff702470ab + version: 1.0.30001760 + resolution: "caniuse-lite@npm:1.0.30001760" + checksum: 10/ac5c13d00b946c3ace331d25379ed9d85743b1028aba79ae80402cb128b4b491122585144898fefb836d4d9879c13c717a081ae241a00420cbbc7e1b934ec685 languageName: node linkType: hard @@ -2515,13 +2524,13 @@ __metadata: "@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" "@types/mdx": "npm:^2.0.13" - "@types/node": "npm:^20" + "@types/node": "npm:^22.13.14" "@types/react": "npm:19.1.9" "@types/react-dom": "npm:19.1.7" "@uiw/codemirror-themes": "npm:^4.23.7" @@ -2529,17 +2538,17 @@ __metadata: chokidar: "npm:^3.6.0" 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.9" next-mdx-remote-client: "npm:^2.1.2" prop-types: "npm:^15.8.1" react: "npm:19.1.1" react-dom: "npm:19.1.1" shiki: "npm:^3.13.0" - storybook: "npm:^8.6.12" + storybook: "npm:^8.6.15" typescript: "npm:^5" unified: "npm:^11.0.4" languageName: unknown @@ -2942,11 +2951,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" @@ -2962,7 +2971,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10/6c21254d3383b9158ff5f3b2881cc702bee3d2635b4326757965945691f6e65e25fdfef4f2964382fb4b2f52d9f03b929cb71d267709727df7365e7da80c8c3a + checksum: 10/fd1813c7ef4e33ec297b86dd0de75879e10514b12cb04a103e7a5b8c5f27d2ea0a91dd15e6d530006c9fe3e7e18f815e900bd6ba117c3b8fbe311c38c340d899 languageName: node linkType: hard @@ -3451,9 +3460,9 @@ __metadata: languageName: node linkType: hard -"framer-motion@npm:^12.23.24": - version: 12.23.24 - resolution: "framer-motion@npm:12.23.24" +"framer-motion@npm:^12.23.26": + version: 12.23.26 + resolution: "framer-motion@npm:12.23.26" dependencies: motion-dom: "npm:^12.23.23" motion-utils: "npm:^12.23.6" @@ -3469,7 +3478,7 @@ __metadata: optional: true react-dom: optional: true - checksum: 10/c46015626d140557f3f225ffeaea14146988a42a99b33e46e3efbab88814343867a63014544169283b9518e1a3abed231ccefd890e601cf66b6c43425b0465e2 + checksum: 10/29d51ac57ce8c2599664bf706d7a83c2aba4fe1dee1c0c7d75d5c45c120b1c2ca5d257c3ff608992b26bf8e9cf258cacf231b0873d7b16f01c5eeb42b9831f78 languageName: node linkType: hard @@ -4735,8 +4744,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" @@ -4747,7 +4756,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 @@ -5265,10 +5274,10 @@ __metadata: linkType: hard "motion@npm:^12.4.1": - version: 12.23.24 - resolution: "motion@npm:12.23.24" + version: 12.23.26 + resolution: "motion@npm:12.23.26" dependencies: - framer-motion: "npm:^12.23.24" + framer-motion: "npm:^12.23.26" tslib: "npm:^2.4.0" peerDependencies: "@emotion/is-prop-valid": "*" @@ -5281,7 +5290,7 @@ __metadata: optional: true react-dom: optional: true - checksum: 10/d63d951676058001c6f234a6bdf8ed5a8fd222a38c1871ef209ed1fd87f2a288ec0e267fa616d80ac8837d0836a5f93ae85a80d773314517f19f304f25b81c3d + checksum: 10/c5e683d2c67e55816587cbbd451c5600dc0602aa04108ab0bf8d4472befd490e90b3177362dbc8b9f96316ec16383924cc574eb7c9e031540f1e88e64a2704ae languageName: node linkType: hard @@ -5342,19 +5351,19 @@ __metadata: languageName: node linkType: hard -"next@npm:15.4.7": - version: 15.4.7 - resolution: "next@npm:15.4.7" +"next@npm:15.5.9": + version: 15.5.9 + resolution: "next@npm:15.5.9" 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.9" + "@next/swc-darwin-arm64": "npm:15.5.7" + "@next/swc-darwin-x64": "npm:15.5.7" + "@next/swc-linux-arm64-gnu": "npm:15.5.7" + "@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" @@ -5397,7 +5406,7 @@ __metadata: optional: true bin: next: dist/bin/next - checksum: 10/e611751247d5cfff9337a84979918bcd61919d7b66a411c982569336c3dba7e7a8704eead7e082e579807200fa8bc80602a3e2ce4423a8b5387aab3353400918 + checksum: 10/ac27b82de08c9720e8e99cd64102af5e30306a8c861630d50da347a02c288cc441273ada64875c47e7a4d5991ff69cc92b23e906ce3271f9835a7911a0f060cc languageName: node linkType: hard @@ -5537,14 +5546,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 @@ -6312,18 +6321,18 @@ __metadata: 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 @@ -6473,11 +6482,11 @@ __metadata: languageName: node linkType: hard -"storybook@npm:^8.6.12": - version: 8.6.14 - resolution: "storybook@npm:8.6.14" +"storybook@npm:^8.6.15": + version: 8.6.15 + resolution: "storybook@npm:8.6.15" dependencies: - "@storybook/core": "npm:8.6.14" + "@storybook/core": "npm:8.6.15" peerDependencies: prettier: ^2 || ^3 peerDependenciesMeta: @@ -6487,7 +6496,7 @@ __metadata: getstorybook: ./bin/index.cjs sb: ./bin/index.cjs storybook: ./bin/index.cjs - checksum: 10/104932fe29ebf49bef24c90285741cec964d1c36b3f0b38da1dace31ac664be457f2a510e41ba69fe8aa0d90c20e3446fec27d1ced41e5eefb3a9ca713d99e79 + checksum: 10/15762c79ec8444a46bc14cddfadbdd54dfd379828acd38555887a246c01e7c9ebb61e4eafafe04efb3ddf6278fb47035216e7f7d9f94fc205da148870173abdf languageName: node linkType: hard diff --git a/docs/api/utility-apis.md b/docs/api/utility-apis.md index fbb71221fc..8d882227ec 100644 --- a/docs/api/utility-apis.md +++ b/docs/api/utility-apis.md @@ -10,31 +10,31 @@ Backstage plugins strive to be self-contained, with as much functionality as pos Backstage provides two primary methods for plugins to communicate across their boundaries in client-side code. The first one being the -[`createPlugin`](../reference/core-plugin-api.createplugin.md) API along with the +[`createPlugin`](https://backstage.io/api/stable/functions/_backstage_core-plugin-api.index.createPlugin.html) API along with the extensions that it can provide, and the second one being Utility APIs. While the -[`createPlugin`](../reference/core-plugin-api.createplugin.md) API is focused on +[`createPlugin`](https://backstage.io/api/stable/functions/_backstage_core-plugin-api.index.createPlugin.html) API is focused on the initialization plugins and the app, the Utility APIs provide ways for plugins to communicate during their entire life cycle. ## Consuming APIs -Each Utility API is tied to an [`ApiRef`](../reference/core-plugin-api.apiref.md) +Each Utility API is tied to an [`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html) instance, which is a global singleton object without any additional state or functionality, its only purpose is to reference Utility APIs. -[`ApiRef`](../reference/core-plugin-api.apiref.md)s are created using -[`createApiRef`](../reference/core-plugin-api.createapiref.md), which is exported -by [`@backstage/core-plugin-api`](../reference/core-plugin-api.md). There are also +[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html)s are created using +[`createApiRef`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.createApiRef.html), which is exported +by [`@backstage/core-plugin-api`](https://backstage.io/api/stable/modules/_backstage_core-plugin-api.html). There are also many predefined Utility APIs in -[`@backstage/core-plugin-api`](../reference/core-plugin-api.md), and they're all +[`@backstage/core-plugin-api`](https://backstage.io/api/stable/modules/_backstage_core-plugin-api.html), and they're all exported with a name of the pattern `*ApiRef`, for example -[`errorApiRef`](../reference/core-plugin-api.errorapiref.md). +[`errorApiRef`](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.errorApiRef.html). To access one of the Utility APIs inside a React component, use the -[`useApi`](../reference/core-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 +[`useApi`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.useApi.html) hook exported by +[`@backstage/core-plugin-api`](https://backstage.io/api/stable/modules/_backstage_core-plugin-api.html), or the +[`withApis`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.withApis.html) HOC if you prefer class components. For example, the -[`ErrorApi`](../reference/core-plugin-api.errorapi.md) can be accessed like this: +[`ErrorApi`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ErrorApi.html) can be accessed like this: ```tsx import { useApi, errorApiRef } from '@backstage/core-plugin-api'; @@ -52,14 +52,14 @@ export const MyComponent = () => { ``` Note that there is no explicit type given for -[`ErrorApi`](../reference/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`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ErrorApi.html). This is because the +[`errorApiRef`](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.errorApiRef.html) has the type +embedded, and [`useApi`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.useApi.html) is able to infer the type. Also note that consuming Utility APIs is not limited to plugins; it can be done from any component inside Backstage, including the ones in -[`@backstage/core-plugin-api`](../reference/core-plugin-api.md). The only +[`@backstage/core-plugin-api`](https://backstage.io/api/stable/modules/_backstage_core-plugin-api.html). The only requirement is that they are beneath the `AppProvider` in the react tree. ## Supplying APIs @@ -67,15 +67,15 @@ requirement is that they are beneath the `AppProvider` in the react tree. ### API Factories APIs are registered in the form of -[`ApiFactory`](../reference/core-plugin-api.apifactory.md) instances, which encapsulate +[`ApiFactory`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiFactory.html) instances, which encapsulate the process of instantiating an API. It is a collection of three things: the -[`ApiRef`](../reference/core-plugin-api.apiref.md) of the API to instantiate, a +[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html) of the API to instantiate, a list of all required dependencies, and a factory function that returns a new API instance. For example, this is the default -[`ApiFactory`](../reference/core-plugin-api.apifactory.md) for the -[`ErrorApi`](../reference/core-plugin-api.errorapi.md): +[`ApiFactory`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiFactory.html) for the +[`ErrorApi`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ErrorApi.html): ```ts createApiFactory({ @@ -89,25 +89,25 @@ createApiFactory({ }); ``` -In this example, the [`errorApiRef`](../reference/core-plugin-api.errorapiref.md) +In this example, the [`errorApiRef`](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.errorApiRef.html) is our API, which encapsulates the -[`ErrorApi`](../reference/core-plugin-api.errorapi.md) type. The -[`alertApiRef`](../reference/core-plugin-api.alertapiref.md) is our single +[`ErrorApi`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ErrorApi.html) type. The +[`alertApiRef`](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.alertApiRef.html) is our single dependency, which we give the name `alertApi`, and is then passed on to the factory function, which returns an implementation of the -[`ErrorApi`](../reference/core-plugin-api.errorapi.md). +[`ErrorApi`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ErrorApi.html). -The [`createApiFactory`](../reference/core-plugin-api.createapifactory.md) +The [`createApiFactory`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.createApiFactory.html) function is a thin wrapper that enables TypeScript type inference. You may notice that there are no type annotations in the above example, and that is because we're able to infer all types from the -[`ApiRef`](../reference/core-plugin-api.apiref.md)s. TypeScript will make sure +[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html)s. TypeScript will make sure that the return value of the `factory` function matches the type embedded in -`api`'s [`ApiRef`](../reference/core-plugin-api.apiref.md), in this case the -[`ErrorApi`](../reference/core-plugin-api.errorapi.md). It will also match the +`api`'s [`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html), in this case the +[`ErrorApi`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ErrorApi.html). It will also match the types between the `deps` and the parameters of the `factory` function, again using the type embedded within the -[`ApiRef`](../reference/core-plugin-api.apiref.md)s. +[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html)s. ## Registering API Factories @@ -119,13 +119,13 @@ app, and the app itself. Starting with the Backstage core library, it provides implementations for all of the core APIs. The core APIs are the ones exported by -[`@backstage/core-plugin-api`](../reference/core-plugin-api.md), such as the -[`errorApiRef`](../reference/core-plugin-api.errorapiref.md) and -[`configApiRef`](../reference/core-plugin-api.configapiref.md). +[`@backstage/core-plugin-api`](https://backstage.io/api/stable/modules/_backstage_core-plugin-api.html), such as the +[`errorApiRef`](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.errorApiRef.html) and +[`configApiRef`](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.configApiRef.html). The core APIs are loaded for any app created with -[`createApp`](../reference/app-defaults.createapp.md) from -[`@backstage/core-plugin-api`](../reference/app-defaults.md), which means that +[`createApp`](https://backstage.io/api/stable/functions/_backstage_app-defaults.createApp.html) from +[`@backstage/core-plugin-api`](https://backstage.io/api/stable/modules/_backstage_core-plugin-api.html), which means that there is no step that needs to be taken to include these APIs in an app. ### Plugin APIs @@ -133,13 +133,13 @@ there is no step that needs to be taken to include these APIs in an app. In addition to the core APIs, plugins can define and export their own APIs. While doing so, they should usually also provide default implementations of their own APIs; for example, the `catalog` plugin exports `catalogApiRef` and also -supplies a default [`ApiFactory`](../reference/core-plugin-api.apifactory.md) of +supplies a default [`ApiFactory`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiFactory.html) of that API using the `CatalogClient`. There is one restriction to plugin-provided API Factories: plugins may not supply factories for core APIs; trying to do so will cause the app to refuse to start. Plugins supply their APIs through the `apis` option of -[`createPlugin`](../reference/core-plugin-api.createplugin.md), for example: +[`createPlugin`](https://backstage.io/api/stable/functions/_backstage_core-plugin-api.index.createPlugin.html), for example: ```ts export const techdocsPlugin = createPlugin({ @@ -164,7 +164,7 @@ Lastly, the app itself is the final point where APIs can be added, and what has the final say in what APIs will be loaded at runtime? The app may override the factories for any of the core or plugin APIs, with the exception of the config, app theme, and identity APIs. These are static APIs that are tied into the -[`createApp`](../reference/app-defaults.createapp.md) implementation and +[`createApp`](https://backstage.io/api/stable/functions/_backstage_app-defaults.createApp.html) implementation and therefore, not possible to override. Overriding APIs is useful for apps that want to switch out behavior to tailor it @@ -227,33 +227,33 @@ const app = createApp({ ``` Note that the above line will cause an error if `IgnoreErrorApi` does not fully -implement the [`ErrorApi`](../reference/core-plugin-api.errorapi.md), as it is +implement the [`ErrorApi`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ErrorApi.html), as it is checked by the type embedded in the -[`errorApiRef`](../reference/core-plugin-api.errorapiref.md) at compile time. +[`errorApiRef`](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.errorApiRef.html) at compile time. ## Defining custom Utility APIs Plugins are free to define their own Utility APIs. Simply define the TypeScript interface for the API and create an -[`ApiRef`](../reference/core-plugin-api.apiref.md) using -[`createApiRef`](../reference/core-plugin-api.createapiref.md) exported from -[`@backstage/core-plugin-api`](../reference/core-plugin-api.md). Also, be sure to +[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html) using +[`createApiRef`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.createApiRef.html) exported from +[`@backstage/core-plugin-api`](https://backstage.io/api/stable/modules/_backstage_core-plugin-api.html). Also, be sure to provide at least one implementation of the API and to declare a default factory -for the API in [`createPlugin`](../reference/core-plugin-api.createplugin.md). +for the API in [`createPlugin`](https://backstage.io/api/stable/functions/_backstage_core-plugin-api.index.createPlugin.html). Custom Utility APIs can be either public or private, which is up to the plugin to choose. Private APIs do not expose an external API surface, and it's therefore possible to make breaking changes to the API without affecting other users of the plugin. If an API is made public, however, it opens up for other plugins to make use of the API, and it also makes it possible for users for your plugin to override the API in the app. It is, however, important to maintain backward compatibility of public APIs, as you may otherwise break apps that are using your plugin. To make an API public, simply export the -[`ApiRef`](../reference/core-plugin-api.apiref.md) of the API, and any associated +[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html) of the API, and any associated types. To make an API private, just avoid exporting the -[`ApiRef`](../reference/core-plugin-api.apiref.md), but still be sure to supply a -default factory to [`createPlugin`](../reference/core-plugin-api.createplugin.md). +[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html), but still be sure to supply a +default factory to [`createPlugin`](https://backstage.io/api/stable/functions/_backstage_core-plugin-api.index.createPlugin.html). Private APIs are useful for plugins that want to depend on other APIs outside of React components, but not have to expose an entire API surface to maintain. When using private APIs, it is fine to use the `typeof` of an implementing class as the type parameter passed to -[`createApiRef`](../reference/core-plugin-api.createapiref.md), while public APIs +[`createApiRef`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.createApiRef.html), while public APIs should always define a separate TypeScript interface type. Plugins may depend on APIs from other plugins, both in React components and as @@ -262,13 +262,13 @@ dependencies between plugins. ## Architecture -The [`ApiRef`](../reference/core-plugin-api.apiref.md) instances mentioned above +The [`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html) instances mentioned above provide a point of indirection between consumers and producers of Utility APIs. It allows for plugins and components to depend on APIs in a type-safe way, without having a direct reference to a concrete implementation of the APIs. The Apps are also given a lot of flexibility in what implementations to provide. As long as they adhere to the contract established by an -[`ApiRef`](../reference/core-plugin-api.apiref.md), they are free to choose any +[`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html), they are free to choose any implementation they want. The figure below shows the relationship between @@ -291,16 +291,16 @@ The indirection provided by Utility APIs also makes it straightforward to test components that depend on APIs, and to provide a standard common development environment for plugins. A proper test wrapper with mocked API implementations is not yet ready, but it will be provided as a part of -[`@backstage/test-utils`](../reference/test-utils.md). It will provide mocked +[`@backstage/test-utils`](https://backstage.io/api/stable/modules/_backstage_test-utils.html). It will provide mocked variants of APIs, with additional methods for asserting a component's interaction with the API. The common development environment for plugins is included in -[`@backstage/dev-utils`](../reference/dev-utils.md), where the exported -[`createDevApp`](../reference/dev-utils.createdevapp.md) function creates an +[`@backstage/dev-utils`](https://backstage.io/api/stable/modules/_backstage_dev-utils.html), where the exported +[`createDevApp`](https://backstage.io/api/stable/functions/_backstage_dev-utils.createDevApp.html) function creates an application with implementations for all core APIs already present. Contrary to the method for wiring up Utility API implementations in an app created with -[`createApp`](../reference/app-defaults.createapp.md), -[`createDevApp`](../reference/dev-utils.createdevapp.md) uses automatic dependency +[`createApp`](https://backstage.io/api/stable/functions/_backstage_app-defaults.createApp.html), +[`createDevApp`](https://backstage.io/api/stable/functions/_backstage_dev-utils.createDevApp.html) uses automatic dependency injection. This is to make it possible to replace any API implementation, and having that be reflected in dependents of that API. diff --git a/docs/auth/index.md b/docs/auth/index.md index bff1f3df26..1d668f6961 100644 --- a/docs/auth/index.md +++ b/docs/auth/index.md @@ -340,7 +340,7 @@ The method with which frontend plugins request access to third-party services is through [Utility APIs](../api/utility-apis.md) for each service provider. These are all suffixed with `*AuthApiRef`, for example `githubAuthApiRef`. For a full list of providers, see the -[@backstage/core-plugin-api](../reference/core-plugin-api.md#variables) reference. +[@backstage/core-plugin-api](https://backstage.io/api/stable/modules/_backstage_core-plugin-api.index.html#alertapiref) reference. ## Custom Authentication Provider @@ -459,6 +459,42 @@ providerFactories: { }, ``` +In the new backend system you can leverage the `authProvidersExtensionPoint` for this: + +```ts +// your-auth-plugin-module.ts +export const gheAuth = createBackendModule({ + // This ID must be exactly "auth" because that's the plugin it targets + pluginId: 'auth', + // This ID must be unique, but can be anything + moduleId: 'ghe-auth-provider', + register(reg) { + reg.registerInit({ + deps: { + providers: authProvidersExtensionPoint, + logger: coreServices.logger, + }, + async init({ providers, logger }) { + providers.registerProvider({ + // This ID must match the actual provider config, e.g. addressing + // auth.providers.ghe means that this must be "ghe". + providerId: 'ghe', + factory: createOAuthProviderFactory({ + authenticator: githubAuthenticator, + signInResolverFactories: { + ...commonSignInResolvers, + }, + }), + }); + }, + }); + }, +}); + +// backend index.ts +backend.add(gheAuth); +``` + ## Configuring token issuers By default, the Backstage authentication backend generates and manages its own signing keys automatically for any issued diff --git a/docs/auth/oidc.md b/docs/auth/oidc.md index 7c4b09d9bf..55827ab1dc 100644 --- a/docs/auth/oidc.md +++ b/docs/auth/oidc.md @@ -241,6 +241,12 @@ These parameters have implicit default values. Don't override them unless you kn - `prompt`: Recommended to use `auto` so the browser will request sign-in to the IDP if the user has no active session. - `sessionDuration`: Lifespan of the user session. +- `startUrlSearchParams`: This is a dictionary of search (query) parameters for the OIDC + authorization start URL. Don't define it unless you want to change the identity + provider's behavior. (For example, you could set the `organization` parameter to guide + users towards a particular sign-in option that your organization prefers.) **Note:** the + start URL is controlled by the browser, so this feature is only for improving the + Backstage user experience. :::note Config Reloading Backstage does not yet support hot reloading of auth provider configuration. Any changes to this YAML file require a restart of Backstage. diff --git a/docs/backend-system/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/cache.md b/docs/backend-system/core-services/cache.md index ad4d04b50a..a23332409b 100644 --- a/docs/backend-system/core-services/cache.md +++ b/docs/backend-system/core-services/cache.md @@ -11,24 +11,84 @@ This service lets your plugin interact with a cache. It is bound to your plugin The cache service can be configured using the `backend.cache` section in your `app-config.yaml`: +### In-Memory (default) + ```yaml backend: cache: - store: redis # or 'valkey', 'memcache', 'memory' + store: memory +``` + +### Memcache + +```yaml +backend: + cache: + store: memcache + connection: user:pass@cache.example.com:11211 +``` + +### Redis + +```yaml +backend: + cache: + store: redis connection: redis://localhost:6379 - # Store-specific configuration (Redis/Valkey only) + # Store-specific configuration (optional) redis: client: - # Optional: Global namespace prefix for all cache keys + # Global namespace prefix for all cache keys namespace: 'my-app' - # Optional: Separator used between namespace and plugin ID (default: ':') + # Separator used between namespace and plugin ID (default: ':') keyPrefixSeparator: ':' # Other Redis-specific options... clearBatchSize: 1000 useUnlink: false ``` +### Valkey + +```yaml +backend: + cache: + store: valkey + connection: redis://localhost:6379 + + # Store-specific configuration (optional) + valkey: + # Global namespace prefix for all cache keys (including separator used between namespace and plugin ID) + keyPrefix: 'my-app:' +``` + +### Infinispan + +```yaml +backend: + cache: + store: infinispan + + # Store-specific configuration (optional) + infinispan: + servers: + # IP address or hostname of the server (default: '127.0.0.1') + - host: 127.0.0.1 + # Port number of the server (default: '11222') + port: 11222 + # Name of the cache (default: 'cache') + cacheName: cache + mediaType: application/json + authentication: + # Whether authentication is enabled (default: 'false') + enabled: true + userName: yourusername + password: yourpassword + saslMechanism: PLAIN +``` + +A full list of configuration items is available [here](https://docs.jboss.org/infinispan/hotrod-clients/javascript/1.0/apidocs/module-infinispan.html), including support for backup clusters. + ### Namespace Configuration For Redis and Valkey stores, you can configure a global namespace that will be prefixed to all cache keys: @@ -36,9 +96,10 @@ For Redis and Valkey stores, you can configure a global namespace that will be p - **Without namespace**: Cache keys use only the plugin ID (e.g., `catalog:some-key`) - **With namespace**: Cache keys use the format `namespace:pluginId:key` (e.g., `my-app:catalog:some-key`) -The `keyPrefixSeparator` controls what character is used between the namespace and plugin ID (defaults to `:`). +For **Redis**, `keyPrefixSeparator` controls what character is used between the namespace and plugin ID (defaults to `:`). +For **Valkey**, you set the full `keyPrefix` including the separator. -**Note**: Memory and Memcache stores do not support namespace configuration and will always use the plugin ID directly. +**Note**: In-memory, Memcache and Infinispan stores do not support namespace configuration and will always use the plugin ID directly. ## Using the service diff --git a/docs/conf/reading.md b/docs/conf/reading.md index 61cff2502b..b6119b0da9 100644 --- a/docs/conf/reading.md +++ b/docs/conf/reading.md @@ -7,7 +7,7 @@ description: Documentation on Reading Backstage Configuration ## Config API There's a common configuration API for by both frontend and backend plugins. An -API reference can be found [here](../reference/config.config.md). +API reference can be found [here](https://backstage.io/api/stable/types/_backstage_config.Config.html). The configuration API is tailored towards failing fast in case of missing or bad config. That's because configuration errors can always be considered programming @@ -115,7 +115,7 @@ example `getString`. These will throw an error if there is no value available. ## Accessing ConfigApi in Frontend Plugins -The [ConfigApi](../reference/core-plugin-api.configapi.md) in the frontend is a +The [ConfigApi](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ConfigApi.html) in the frontend is a [UtilityApi](../api/utility-apis.md). It's accessible as usual via the `configApiRef` exported from `@backstage/core-plugin-api`: diff --git a/docs/conf/user-interface/index.md b/docs/conf/user-interface/index.md index 81acfdf720..e0fb645d78 100644 --- a/docs/conf/user-interface/index.md +++ b/docs/conf/user-interface/index.md @@ -103,13 +103,13 @@ Backstage UI is using light by default under `:root` but you can target it more [data-theme-mode='light'] { /* Light theme specific styles */ -  --bui-bg: #f8f8f8; +  --bui-bg-surface-0: #f8f8f8; --bui-fg-primary: #000; } [data-theme-mode='dark'] { /* Dark theme specific styles */ -  --bui-bg: #333333; +  --bui-bg-surface-0: #333333; --bui-fg-primary: #fff; } ``` @@ -122,18 +122,18 @@ We recommend starting with a core set of CSS variables to quickly achieve a bran And if you’d like to go even further, you can target specific component class names for advanced customization. -| Token Name | Description | -| -------------------- | ----------------------------------------------------------------------------------- | -| `--bui-bg` | This is used to define the background color of your app. It will only be used once. | -| `--bui-bg-surface-1` | We ar using this color to sit on top of `--bui-bg` mostly for `Card`, `Dialog`, ... | -| `--bui-bg-surface-2` | This is for content inside elevated components. This colour is less common. | -| `--bui-bg-solid` | This is used for main actions like primary buttons. | -| `--bui-fg-solid` | This is for texts or icons on top of a solid backgrounds. | -| `--bui-fg-primary` | Your primary text or icon colours. | -| `--bui-fg-secondary` | Your secondary text or icon colours. | -| `--bui-fg-link` | Used for links. | -| `--bui-border` | Main borders around surfaces like `Card`, `Dialog`, ... | -| `--bui-font-regular` | The main font of your app. | +| Token Name | Description | +| -------------------- | --------------------------------------------------------------------------------------------- | +| `--bui-bg-surface-0` | This is used to define the background color of your app. It will only be used once. | +| `--bui-bg-surface-1` | We ar using this color to sit on top of `--bui-bg-surface-0` mostly for `Card`, `Dialog`, ... | +| `--bui-bg-surface-2` | This is for content inside elevated components. This colour is less common. | +| `--bui-bg-solid` | This is used for main actions like primary buttons. | +| `--bui-fg-solid` | This is for texts or icons on top of a solid backgrounds. | +| `--bui-fg-primary` | Your primary text or icon colours. | +| `--bui-fg-secondary` | Your secondary text or icon colours. | +| `--bui-fg-link` | Used for links. | +| `--bui-border` | Main borders around surfaces like `Card`, `Dialog`, ... | +| `--bui-font-regular` | The main font of your app. |
All available CSS variables @@ -157,13 +157,14 @@ These colors are used for special purposes like ring, scrollbar, ... #### Core background colors -These colors are used for the background of your application. We are mostly using for now a single elevated background for panels. `--bui-bg` should mostly use as the main background color of your app. +These colors are used for the background of your application. We are mostly using for now a single elevated background for panels. `--bui-bg-surface-0` should mostly use as the main background color of your app. | Token Name | Description | | ------------------------- | ------------------------------------------------ | -| `--bui-bg` | The background color of your Backstage instance. | +| `--bui-bg-surface-0` | The background color of your Backstage instance. | | `--bui-bg-surface-1` | Use for any panels or elevated surfaces. | | `--bui-bg-surface-2` | Use for any panels or elevated surfaces. | +| `--bui-bg-surface-3` | Use for any panels or elevated surfaces. | | `--bui-bg-solid` | Used for solid background colors. | | `--bui-bg-solid-hover` | Used for solid background colors when hovered. | | `--bui-bg-solid-pressed` | Used for solid background colors when pressed. | diff --git a/docs/deployment/scaling.md b/docs/deployment/scaling.md index 2c3b687f67..dd765d3c1a 100644 --- a/docs/deployment/scaling.md +++ b/docs/deployment/scaling.md @@ -17,7 +17,7 @@ into multiple different services, each running a different set of plugins. This is a more advanced approach and requires you to be able to route requests to the appropriate backends based on the plugin ID. Both for ingress, but also internal traffic between Backstage backends, which is done by creating a custom -implementation of the [DiscoveryService](../reference/backend-plugin-api.discoveryservice.md) interface. See the [backend system docs](../backend-system/building-backends/01-index.md#split-into-multiple-backends) for more details on how to separate your deployment into multiple backend instances. +implementation of the [DiscoveryService](https://backstage.io/api/stable/interfaces/_backstage_backend-plugin-api.index.DiscoveryService.html) interface. See the [backend system docs](../backend-system/building-backends/01-index.md#split-into-multiple-backends) for more details on how to separate your deployment into multiple backend instances. Lastly, you can also replicate the Backstage deployments across multiple regions. This is not a pattern that there is built-in support for and typically only makes diff --git a/docs/features/software-templates/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/features/software-templates/ui-options-examples.md b/docs/features/software-templates/ui-options-examples.md index 1a024569a6..845da8e60b 100644 --- a/docs/features/software-templates/ui-options-examples.md +++ b/docs/features/software-templates/ui-options-examples.md @@ -468,3 +468,59 @@ repoUrl: The supported `additionalScopes` values are `gerrit`, `github`, `gitlab`, `bitbucket`, and `azure`. If you're also using the `RepoUrlPicker` field extension, you should simply duplicate this part from there. + +## RepoOwnerPicker + +The input props that can be specified under `ui:options` for the `RepoOwnerPicker` field extension. + +### `host` + +The SCM integration host that owners should be fetched from for autocompletion. + +- Fetch owners from `github.com` + +```yaml +repoUrl: + title: Repository Owner + type: string + ui:field: RepoOwnerPicker + ui:options: + host: github.com +``` + +### `excludedOwners` + +List of owners that should be excluded from autocompletion. + +- Exclude owner `owner_1` from autocompletion + +```yaml +repoUrl: + title: Repository Owner + type: string + ui:field: RepoOwnerPicker + ui:options: + excludedOwners: + - owner_1 +``` + +### `requestUserCredentials` + +If defined will request user credentials to auth against the given SCM platform. + +```yaml +repoUrl: + title: Repository Owner + type: string + ui:field: RepoOwnerPicker + ui:options: + requestUserCredentials: + secretsKey: USER_OAUTH_TOKEN + additionalScopes: + github: + - workflow:write +``` + +`secretsKey` is the key used within the template secrets context to store the credential and `additionalScopes` is any additional permission scopes to request. + +The supported `additionalScopes` values are `gerrit`, `github`, `gitlab`, `bitbucket`, and `azure`. diff --git a/docs/features/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md index 32f17f945f..5d9fcba603 100644 --- a/docs/features/software-templates/writing-templates.md +++ b/docs/features/software-templates/writing-templates.md @@ -555,6 +555,35 @@ The `RepoBranchPicker` is a custom field that we provide part of the `plugin-scaffolder`. You can provide your own custom fields by [writing your own Custom Field Extensions](./writing-custom-field-extensions.md) +### The Repository Owner Picker + +Similar to the repository picker, there is a picker for owners to support autocompletion. A full example could look like this: + +```yaml +- title: Choose an owner + required: + - repoOwner + properties: + repoOwner: + title: Repository Owner + type: string + ui:field: RepoOwnerPicker + ui:options: + host: github.com + excludedOwners: + - backstage + requestUserCredentials: + secretsKey: USER_OAUTH_TOKEN +``` + +Passing the `requestUserCredentials` and `host` properties is required for autocompletion to work. For more information regarding the `requestUserCredentials` object, please refer to the [Using the Users `oauth` token](#using-the-users-oauth-token) section under [The Repository Picker](#the-repository-picker). + +For a list of all possible `ui:options` input props for `RepoOwnerPicker`, please visit [here](./ui-options-examples.md#repoownerpicker). + +The `RepoOwnerPicker` is a custom field that we provide part of the +`plugin-scaffolder`. You can provide your own custom fields by +[writing your own Custom Field Extensions](./writing-custom-field-extensions.md) + ### Accessing the signed-in users details Sometimes when authoring templates, you'll want to access the user that is running the template, and get details from the profile or the users `Entity` in the Catalog. diff --git a/docs/frontend-system/building-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 2b248b12a7..1fd74becfc 100644 --- a/docs/frontend-system/building-apps/08-migrating.md +++ b/docs/frontend-system/building-apps/08-migrating.md @@ -676,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, }, }), @@ -700,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/01-index.md b/docs/frontend-system/building-plugins/01-index.md index 283c162fa7..fd88478b37 100644 --- a/docs/frontend-system/building-plugins/01-index.md +++ b/docs/frontend-system/building-plugins/01-index.md @@ -220,6 +220,6 @@ export const examplePlugin = createFrontendPlugin({ }); ``` -The `ExampleEntityContent` itself is again a regular React component where you can implement any functionality you want. To access the entity that the content is being rendered for, you can use the `useEntity` hook from `@backstage/plugin-catalog-react`. You can see a full list of APIs provided by the catalog React library in [the API reference](../../reference/plugin-catalog-react.md). +The `ExampleEntityContent` itself is again a regular React component where you can implement any functionality you want. To access the entity that the content is being rendered for, you can use the `useEntity` hook from `@backstage/plugin-catalog-react`. You can see a full list of APIs provided by the catalog React library in [the API reference](https://backstage.io/api/stable/modules/_backstage_plugin-catalog-react.index.html). For a more complete list of the different kinds of extensions that you can create for your plugin, see the [extension blueprints](./03-common-extension-blueprints.md) section. diff --git a/docs/frontend-system/building-plugins/03-common-extension-blueprints.md b/docs/frontend-system/building-plugins/03-common-extension-blueprints.md index 71989e898f..abac1f03f5 100644 --- a/docs/frontend-system/building-plugins/03-common-extension-blueprints.md +++ b/docs/frontend-system/building-plugins/03-common-extension-blueprints.md @@ -11,35 +11,35 @@ This section covers many of the [extension blueprints](../architecture/23-extens These are the [extension blueprints](../architecture/23-extension-blueprints.md) provided by the Backstage frontend framework itself. -### Api - [Reference](../../reference/frontend-plugin-api.apiblueprint.md) +### Api - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.ApiBlueprint.html) An API extension is used to add or override [Utility API factories](../utility-apis/01-index.md) in the app. They are commonly used by plugins for both internal and shared APIs. There are also many built-in Api extensions provided by the framework that you are able to override. -### NavItem - [Reference](../../reference/frontend-plugin-api.navitemblueprint.md) +### NavItem - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.NavItemBlueprint.html) Navigation item extensions are used to provide menu items that link to different parts of the app. By default nav items are attached to the app nav extension, which by default is rendered as the left sidebar in the app. -### Page - [Reference](../../reference/frontend-plugin-api.pageblueprint.md) +### Page - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.PageBlueprint.html) Page extensions provide content for a particular route in the app. By default pages are attached to the app routes extensions, which renders the root routes. -### SignInPage - [Reference](../../reference/frontend-plugin-api.signinpageblueprint.md) +### SignInPage - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.SignInPageBlueprint.html) Sign-in page extension have a single purpose - to implement a custom sign-in page. They are always attached to the app root extension and are rendered before the rest of the app until the user is signed in. -### SwappableComponent - [Reference](../../reference/frontend-plugin-api.swappablecomponentblueprint.md) +### SwappableComponent - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.SwappableComponentBlueprint.html) Swappable Components are extensions that are used to replace the implementations of components in the app and plugins. -### Theme - [Reference](../../reference/frontend-plugin-api.themeblueprint.md) +### Theme - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.ThemeBlueprint.html) Theme extensions provide custom themes for the app. They are always attached to the app extension and you can have any number of themes extensions installed in an app at once, letting the user choose which theme to use. -### Icons - [Reference](../../reference/frontend-plugin-api.iconbundleblueprint.md) +### Icons - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.IconBundleBlueprint.html) Icon bundle extensions provide the ability to replace or provide new icons to the app. You can use the above blueprint to make new extension instances which can be installed into the app. -### Translation - [Reference](../../reference/frontend-plugin-api.translationblueprint.md) +### Translation - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.TranslationBlueprint.html) Translation extension provide custom translation messages for the app. They can be used both to override the default english messages to custom ones, as well as provide translations for additional languages. diff --git a/docs/frontend-system/building-plugins/05-migrating.md b/docs/frontend-system/building-plugins/05-migrating.md index 4940e0526b..b0e9816a17 100644 --- a/docs/frontend-system/building-plugins/05-migrating.md +++ b/docs/frontend-system/building-plugins/05-migrating.md @@ -106,7 +106,6 @@ 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 } from '@backstage/core-compat-api'; const fooPage = PageBlueprint.make({ params: { @@ -116,12 +115,7 @@ const fooPage = PageBlueprint.make({ // 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/discovery.md b/docs/integrations/bitbucketCloud/discovery.md index e0fab954ac..3f5920be5d 100644 --- a/docs/integrations/bitbucketCloud/discovery.md +++ b/docs/integrations/bitbucketCloud/discovery.md @@ -54,6 +54,8 @@ You need to decide how you want to receive events from external sources like - [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) - [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md) +- [via Google Pub/Sub](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-google-pubsub/README.md) +- [via a Kafka topic](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-kafka/README.md) Further documentation: @@ -72,6 +74,8 @@ Additionally, you need to decide how you want to receive events from external so - [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) - [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md) +- [via Google Pub/Sub](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-google-pubsub/README.md) +- [via a Kafka topic](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-kafka/README.md) Set up your provider diff --git a/docs/integrations/bitbucketCloud/locations.md b/docs/integrations/bitbucketCloud/locations.md index 6b735171a4..807076c9e9 100644 --- a/docs/integrations/bitbucketCloud/locations.md +++ b/docs/integrations/bitbucketCloud/locations.md @@ -32,6 +32,15 @@ integrations: appPassword: my-password ``` +OAuth 2.0 client credentials flow: + +```yaml +integrations: + bitbucketCloud: + - clientId: client-id + clientSecret: client-secret +``` + :::note Note A public Bitbucket Cloud provider is added automatically at startup for @@ -41,7 +50,7 @@ convenience, so you only need to list it if you want to supply credentials. :::note Note -The credential required for this type is either an [Api token](https://support.atlassian.com/bitbucket-cloud/docs/using-api-tokens/) or an [App Password](https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/). An Atlassian Account API key will not work. +The credential required for this type is either an [Api token](https://support.atlassian.com/bitbucket-cloud/docs/using-api-tokens/), an [App Password](https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/) or an [OAuth 2.0 client credentials](https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/). An Atlassian Account API key will not work. ::: @@ -55,3 +64,5 @@ This one entry will have the following elements: neither a username nor token are supplied, anonymous access will be used. - `token`: The token used to authenticate requests. - `appPassword`: The app password for the Bitbucket Cloud user. +- `clientId`: The OAuth client ID for Bitbucket Cloud (used with `clientSecret` for OAuth 2.0 client credentials flow). +- `clientSecret`: The OAuth client secret for Bitbucket Cloud (used with `clientId` for OAuth 2.0 client credentials flow). diff --git a/docs/integrations/bitbucketServer/discovery.md b/docs/integrations/bitbucketServer/discovery.md index 350bc33aed..bbdd105af4 100644 --- a/docs/integrations/bitbucketServer/discovery.md +++ b/docs/integrations/bitbucketServer/discovery.md @@ -42,6 +42,8 @@ You need to decide how you want to receive events from external sources like - [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) - [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md) +- [via Google Pub/Sub](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-google-pubsub/README.md) +- [via a Kafka topic](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-kafka/README.md) Further documentation: diff --git a/docs/integrations/github/discovery.md b/docs/integrations/github/discovery.md index f245e960ea..e17f514058 100644 --- a/docs/integrations/github/discovery.md +++ b/docs/integrations/github/discovery.md @@ -141,7 +141,7 @@ events: region: us-east-2 ``` -The [AWS SQS module `README`](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-aws-sqs/README.md#configuration) has more details on the configuration options, the example above includes on the required options. +The [AWS SQS module `README`](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-aws-sqs/README.md#configuration) has more details on the configuration options, the example above includes only the required options. ### Events Setup using Google Pub/Sub module @@ -179,7 +179,53 @@ events: targetTopic: 'github.{{ event.attributes.x-github-event }}' ``` -The [Google Pub/Sub module `README`](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-google-pubsub/README.md#configuration) has more details on the configuration options, the example above includes on the required options. +The [Google Pub/Sub module `README`](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-google-pubsub/README.md#configuration) has more details on the configuration options, the example above includes only the required options. + +### Events Setup using Kafka module + +Alternatively to using the HTTP endpoint you can use the Kafka module, here's how. + +First we need to add the package: + +```bash title="from your Backstage root directory" +yarn --cwd packages/backend add @backstage/plugin-events-backend-module-kafka +``` + +Then we need to add it to your backend: + +```ts title="in packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-events-backend')); +backend.add(import('@backstage/plugin-events-backend-module-github')); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-events-backend-module-kafka')); +/* highlight-add-end */ +``` + +Finally you will want to configure it: + +```yaml title="app-config.yaml +events: + modules: + kafka: + kafkaConsumingEventPublisher: + # Client ID used by Backstage to identify when connecting to the Kafka cluster. + clientId: your-client-id + # List of brokers in the Kafka cluster to connect to. + brokers: + - broker1 + - broker2 + topics: + # Replace with actual topic name as expected by subscribers + - topic: 'backstage.topic' + kafka: + # The Kafka topics to subscribe to. + topics: + - topic1 + # The GroupId to be used by the topic consumers. + groupId: your-group-id +``` + +The [Kafka module `README`](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-kafka/README.md#configuration) has more details on the configuration options, the example above includes only the required options. ## Configuration diff --git a/docs/integrations/github/org.md b/docs/integrations/github/org.md index 6526183f2d..2dac8b8866 100644 --- a/docs/integrations/github/org.md +++ b/docs/integrations/github/org.md @@ -90,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: @@ -106,6 +107,8 @@ Directly under the `githubOrg` is a list of configurations, each entry is a stru 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 The catalog module for GitHub Org comes with events support enabled. @@ -129,6 +132,8 @@ You can decide between the following options (extensible): - [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) - [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md) +- [via Google Pub/Sub](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-google-pubsub/README.md) +- [via a Kafka topic](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-kafka/README.md) You can check the official docs to [configure your webhook](https://docs.github.com/en/developers/webhooks-and-events/webhooks/creating-webhooks) and to [secure your request](https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks). The webhook will need to be configured to forward `organization`,`team` and `membership` events. diff --git a/docs/integrations/gitlab/discovery.md b/docs/integrations/gitlab/discovery.md index 5e02c853fa..3d47edba6d 100644 --- a/docs/integrations/gitlab/discovery.md +++ b/docs/integrations/gitlab/discovery.md @@ -43,6 +43,8 @@ You need to decide how you want to receive events from external sources like - [via HTTP endpoint](https://github.com/backstage/backstage/blob/master/plugins/events-backend/README.md#configuration) - [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md) +- [via Google Pub/Sub](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-google-pubsub/README.md) +- [via a Kafka topic](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-kafka/README.md) Further documentation: @@ -92,6 +94,8 @@ Additionally, you need to decide how you want to receive events from external so - [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) - [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md) +- [via Google Pub/Sub](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-google-pubsub/README.md) +- [via a Kafka topic](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-kafka/README.md) Set up your provider diff --git a/docs/integrations/gitlab/org.md b/docs/integrations/gitlab/org.md index 8249a97c24..2908bb7c09 100644 --- a/docs/integrations/gitlab/org.md +++ b/docs/integrations/gitlab/org.md @@ -49,6 +49,8 @@ You need to decide how you want to receive events from external sources like - [via HTTP endpoint](https://github.com/backstage/backstage/blob/master/plugins/events-backend/README.md#configuration) - [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md) +- [via Google Pub/Sub](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-google-pubsub/README.md) +- [via a Kafka topic](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-kafka/README.md) Further documentation: @@ -101,6 +103,8 @@ Additionally, you need to decide how you want to receive events from external so - [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) - [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md) +- [via Google Pub/Sub](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-google-pubsub/README.md) +- [via a Kafka topic](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-kafka/README.md) Set up your provider diff --git a/docs/notifications/processors.md b/docs/notifications/processors.md index c25b669ed6..af0b9f8f93 100644 --- a/docs/notifications/processors.md +++ b/docs/notifications/processors.md @@ -149,11 +149,63 @@ notifications: broadcastChannels: # Optional, if you wish to support broadcast notifications. - C12345678 username: 'Backstage Bot' # Optional, defaults to the name of the Slack App. + concurrencyLimit: 20 # Optional, number of messages allowed per interval. Defaults to 10. + throttleInterval: 1m # Optional, Accepts ISO-8601 duration, ms-style ("1m", "30s"), or HumanDuration ({ minutes: 2 }). Defaults to 1 minute ``` Multiple instances can be added in the `slack` array, allowing you to have multiple configurations if you need to send messages to more than one Slack workspace. Org-Wide App installation is not currently supported. +### Broadcast Channel Routing + +For more granular control over where broadcast notifications are sent, you can use `broadcastRoutes` to route notifications to different Slack channels based on their origin and/or topic. This is useful when you want different types of notifications to go to different channels. + +```yaml +notifications: + processors: + slack: + - token: xoxb-XXXXXXXXX + # Legacy option - used as fallback when no routes match + broadcastChannels: + - general-notifications + # Route broadcasts based on origin and/or topic + broadcastRoutes: + # Most specific: matches both origin AND topic + - origin: plugin:catalog + topic: alerts + channel: catalog-alerts + # Origin only: all notifications from this origin + - origin: plugin:catalog + channel: catalog-updates + # Topic only: all notifications with this topic (any origin) + - topic: security + channel: security-team + # Multiple channels: send to several channels at once + - origin: external:monitoring + channel: + - ops-team + - on-call-alerts +``` + +#### Route Matching Precedence + +Routes are evaluated in the following order of priority: + +1. **Origin + Topic match** (most specific) - A route that specifies both `origin` and `topic` will match first +2. **Origin-only match** - A route with only `origin` specified (no `topic`) +3. **Topic-only match** - A route with only `topic` specified (no `origin`) +4. **Default fallback** - If no routes match, falls back to `broadcastChannels` + +The first matching route wins. If no routes match and no `broadcastChannels` are configured, the broadcast notification will not be sent to Slack. + +#### Configuration Options + +| Property | Type | Description | +| --------- | ---------------------- | ------------------------------------------------------------------------------------------ | +| `origin` | `string` | Optional. The notification origin to match (e.g., `plugin:catalog`, `external:my-service`) | +| `topic` | `string` | Optional. The notification topic to match (e.g., `alerts`, `updates`, `security`) | +| `channel` | `string` or `string[]` | Required. The Slack channel(s) to send to. Can be channel IDs, channel names, or user IDs | + ### Entity Requirements Entities must be annotated with the following annotation: diff --git a/docs/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/integrating-plugin-into-software-catalog.md b/docs/plugins/integrating-plugin-into-software-catalog.md index ed2e25c601..3542e073cb 100644 --- a/docs/plugins/integrating-plugin-into-software-catalog.md +++ b/docs/plugins/integrating-plugin-into-software-catalog.md @@ -32,7 +32,7 @@ Creating the plugin... ### Reading entities from within your plugin You can access the currently selected entity using the backstage api -[`useEntity`](../reference/plugin-catalog-react.useentity.md). For example, +[`useEntity`](https://backstage.io/api/stable/functions/_backstage_plugin-catalog-react.index.useEntity.html). For example, ```tsx import { useEntity } from '@backstage/plugin-catalog-react'; diff --git a/docs/plugins/structure-of-a-plugin.md b/docs/plugins/structure-of-a-plugin.md index 0d18942320..7cfaca4561 100644 --- a/docs/plugins/structure-of-a-plugin.md +++ b/docs/plugins/structure-of-a-plugin.md @@ -81,8 +81,8 @@ export const ExamplePage = examplePlugin.provide( ``` This is where the plugin is created and where it creates and exports extensions -that can be imported and used the app. See reference docs for -[`createPlugin`](../reference/core-plugin-api.createplugin.md) or introduction to +that can be imported and used in the app. See reference docs for +[`createPlugin`](https://backstage.io/api/stable/functions/_backstage_core-plugin-api.index.createPlugin.html) or introduction to the new [Composability System](./composability.md). ## Components diff --git a/docs/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.46.0-changelog.md b/docs/releases/v1.46.0-changelog.md new file mode 100644 index 0000000000..22e6e39dab --- /dev/null +++ b/docs/releases/v1.46.0-changelog.md @@ -0,0 +1,2369 @@ +# Release v1.46.0 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.46.0](https://backstage.github.io/upgrade-helper/?to=1.46.0) + +## @backstage/plugin-techdocs-addons-test-utils@2.0.0 + +### Major Changes + +- 8d6709e: **BREAKING**: `TechDocsAddonTester.renderWithEffects()` no longer returns a screen; this means that you can no longer grab assertions such as `getByText` from its return value. + + Newer versions of `@testing-library` recommends using the `screen` export for assertions - and removing this from the addon tester contract allows us to more freely iterate on which underlying version of the testing library is being used. + + One notable effect of this, however, is that the `@testing-library` `screen` does NOT support assertions on the shadow DOM, which techdocs relies on. You will therefore want to add a dependency on [the `shadow-dom-testing-library` package](https://github.com/konnorrogers/shadow-dom-testing-library/) in your tests, and using its `screen` and its dedicated `*Shadow*` methods. As an example, if you keep doing `getByText` you will not get matches inside the shadow DOM - switch to `getByShadowText` instead. + + ```ts + import { screen } from 'shadow-dom-testing-library'; + + // ... render the addon ... + await TechDocsAddonTester.buildAddonsInTechDocs([]) + .withDom(TEST_CONTENT) + .renderWithEffects(); + + expect(screen.getByShadowText('TEST_CONTENT')).toBeInTheDocument(); + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/plugin-techdocs@1.16.1 + - @backstage/plugin-catalog@1.32.1 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-search-react@1.10.1 + - @backstage/test-utils@1.7.14 + - @backstage/core-plugin-api@1.12.1 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-techdocs-react@1.3.6 + +## @backstage/backend-app-api@1.4.0 + +### Minor Changes + +- 5a2d538: Introduced backend startup result tracking and error handling. The `Backend.start()` method now returns a `BackendStartupResult` with detailed success/failure status and timing information for all plugins and modules. When startup fails, a `BackendStartupError` is thrown that includes the complete startup results, making it easier to diagnose which plugins or modules failed. + + This also improves the default error message when backend startup fails, and of course makes it possible to craft your own custom error reporting based on the startup results. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/backend-defaults@0.14.0 + +### Minor Changes + +- fa43826: Move `better-sqlite3` from dependencies to peer dependencies +- 2bc4e02: **BREAKING** The correct configuration options for Valkey are now being used. + + These changes are **required** to `app-config.yaml`: + + ```diff + backend: + cache: + store: valkey + connection: ... + client: + - namespace: 'my-app' + - keyPrefixSeparator: ':' + + keyPrefix: 'my-app:' + - clearBatchSize: 1000 + - useUnlink: false + ``` + + In comparison to Redis, Valkey requires the full `keyPrefix` including the separator to be specified instead of separate `namespace` and `keyPrefixSeparator` options. Also, Valkey does not support the `clearBatchSize` and `useUnlink` options. + +### Patch Changes + +- 37fba1d: Added support for Bitbucket Cloud OAuth. This introduces an alternative authentication method using a workspace OAuth consumer, alongside App Passwords (deprecated) and API tokens. OAuth does not require a bot or service account and avoids token expiry issues. + + **BREAKING CHANGES** + + - **@backstage/integration** (`src/bitbucketCloud/core.ts`) + + - `getBitbucketCloudRequestOptions` now returns a `Promise` and **must** be awaited. + + - **@backstage/plugin-scaffolder-backend-module-bitbucket-cloud** (`src/actions/helpers.ts`) + - `getBitbucketClient` now returns a `Promise` and **must** be awaited. + - `getAuthorizationHeader` now returns a `Promise` and **must** be awaited. + + **OAuth usage example** + + ```yaml + integrations: + bitbucketCloud: + - clientId: client-id + clientSecret: client-secret + ``` + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 + +- aa79251: build(deps): bump `node-forge` from 1.3.1 to 1.3.2 + +- f96edff: Allow configuration of the `referrerPolicy` + +- fb029b6: Updated luxon types + +- d9759a1: **BREAKING ALPHA**: The old `instanceMetadataService` has been removed from alpha. Please switch over to using the stable `coreServices.rootInstanceMetadata` and related types instead, available from `@backstage/backend-plugin-api`. + +- 847a330: Fix for `jose` types + +- 25b560e: Internal change to support new versions of the `logform` library + +- 2a0c4b0: Adds a new experimental `RootSystemMetadataService` for tracking the collection of Backstage instances that may be deployed at any one time. It currently offers a single API, `getInstalledPlugins` that returns a list of installed plugins based on config you have set up in `discovery.endpoints` as well as the plugins installed on the instance you're calling the API with. It does not handle wildcard values or fallback values. The intention is for this plugin to provide plugin authors with a simple interface to fetch a trustworthy list of all installed plugins. + +- 3016a79: Updated dependency `@types/archiver` to `^7.0.0`. + +- 42db6a6: Don't warn when parsing `storeOptions` for `memory` cache + +- Updated dependencies + - @backstage/cli-node@0.2.16 + - @backstage/integration@1.19.0 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-events-node@0.4.18 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-app-api@1.4.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/config-loader@1.10.7 + - @backstage/backend-dev-utils@0.1.6 + +## @backstage/backend-plugin-api@1.6.0 + +### Minor Changes + +- 2a0c4b0: Adds a new experimental `RootSystemMetadataService` for tracking the collection of Backstage instances that may be deployed at any one time. It currently offers a single API, `getInstalledPlugins` that returns a list of installed plugins based on config you have set up in `discovery.endpoints` as well as the plugins installed on the instance you're calling the API with. It does not handle wildcard values or fallback values. The intention is for this plugin to provide plugin authors with a simple interface to fetch a trustworthy list of all installed plugins. + +### Patch Changes + +- d9759a1: **BREAKING ALPHA**: The old `instanceMetadataService` has been removed from alpha. Please switch over to using the stable `coreServices.rootInstanceMetadata` and related types instead, available from `@backstage/backend-plugin-api`. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/cli-common@0.1.16 + +## @backstage/cli@0.35.0 + +### Minor Changes + +- f6f22a9: Provide `--no-node-snapshot` by default when running the `package start` or `package test`. You can disable this behavior by providing `NODE_OPTIONS='--node-snapshot'`. + +- f8dff94: Switched the default module resolution to `bundler` and the `module` setting to `ES2020`. + + You may need to bump some dependencies as part of this change and fix imports in code. The most common source of this is that type checking will now consider the `exports` field in `package.json` when resolving imports. This in turn can break older versions of packages that had incompatible `exports` fields. Generally these issues will have already been fixed in the upstream packages. + + You might be tempted to use `--skipLibCheck` to hide issues due to this change, but it will weaken the type safety of your project. If you run into a large number of issues and want to keep the old behavior, you can reset the `moduleResolution` and `module` settings your own `tsconfig.json` file to `node` and `ESNext` respectively. But keep in mind that the `node` option will be removed in future versions of TypeScript. + + A future version of Backstage will make these new settings mandatory, as we move to rely on the `exports` field for type resolution in packages, rather than the `typesVersions` field. + +- cd0b8a1: **BREAKING**: `jest` is now a peer dependency. If you run tests using Backstage CLI, you must add Jest and its environment dependencies as `devDependencies` in your project. + + You can choose to install either Jest 29 or Jest 30. The built-in Jest version before this change was Jest 29, however, we recommend that you switch to Jest 30. Upgrading will solve the `Could not parse CSS stylesheet` errors, allow you to use MSW v2 in web packages, and ensure that you remain compatible with future versions of the Backstage CLI. Support for Jest 29 is temporary, with the purpose of allowing you to upgrade at your own pace, but it will eventually be removed. + + - **Jest 29**: Install `jest@^29` and `jest-environment-jsdom@^29`. No migration needed, but you may see `Could not parse CSS stylesheet` warnings/errors when testing components from `@backstage/ui` or other packages using CSS `@layer` declarations. + - **Jest 30**: Install `jest@^30`, `@jest/environment-jsdom-abstract@^30`, and `jsdom@^27`. Fixes the stylesheet parsing warnings/errors, but requires migration steps. + + See the [Jest 30 migration guide](https://backstage.io/docs/tutorials/jest30-migration) for detailed migration instructions. + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 + +- e7db290: Add missing peer/dev dependencies to the frontend plugin template. + + `react-dom` was not declared as a peer dependency, causing module resolution + errors when generating plugins outside a Backstage monorepo. This adds + `react-dom` to `peerDependencies` (for consuming apps) and `devDependencies` + (for local development). `react-router-dom` is also added to `peerDependencies` (for consuming apps) and `devDependencies` + to support routing during plugin development. + + Fixes: + + - Module not found: Can't resolve 'react-dom' + - Module not found: Can't resolve 'react-router-dom' + +- 1226647: Updated dependency `esbuild` to `^0.27.0`. + +- f89a074: Updated dependency `@pmmmwh/react-refresh-webpack-plugin` to `^0.6.0`. + +- 2b81751: Updated dependency `webpack` to `~5.103.0`. + +- fafd9e1: Fixed internal usage of `yargs`. + +- c8c2329: Add proxy configuration from env-vars to create-app tasks + +- 2bae83a: Switched compilation target to ES2022 in order to match the new set of supported Node.js versions, which are 22 and 24. + + The TypeScript compilation target has been set to ES2022, because setting it to a higher target will break projects on older TypeScript versions. If you use a newer TypeScript version in your own project, you can bump `compilerOptions.target` to ES2023 or ES2024 in your own `tsconfig.json` file. + +- 7fbac5c: Updated to use new utilities from `@backstage/cli-common`. + +- 2bae83a: Bumped dev dependencies `@types/node` + +- Updated dependencies + - @backstage/cli-node@0.2.16 + - @backstage/integration@1.19.0 + - @backstage/cli-common@0.1.16 + - @backstage/config-loader@1.10.7 + +## @backstage/integration@1.19.0 + +### Minor Changes + +- 37fba1d: Added support for Bitbucket Cloud OAuth. This introduces an alternative authentication method using a workspace OAuth consumer, alongside App Passwords (deprecated) and API tokens. OAuth does not require a bot or service account and avoids token expiry issues. + + **BREAKING CHANGES** + + - **@backstage/integration** (`src/bitbucketCloud/core.ts`) + + - `getBitbucketCloudRequestOptions` now returns a `Promise` and **must** be awaited. + + - **@backstage/plugin-scaffolder-backend-module-bitbucket-cloud** (`src/actions/helpers.ts`) + - `getBitbucketClient` now returns a `Promise` and **must** be awaited. + - `getAuthorizationHeader` now returns a `Promise` and **must** be awaited. + + **OAuth usage example** + + ```yaml + integrations: + bitbucketCloud: + - clientId: client-id + clientSecret: client-secret + ``` + +### Patch Changes + +- a26a322: Added support for using a GitHub App installation to generate tokens for public repository access when the `publicAccess` option is enabled. When all other authentication methods fail (e.g., the app is not installed in that organization), the provider will now use an available installation to generate a token that can be used to access public repositories as read only. +- fb029b6: Updated luxon types +- e15fdae: Made the github urls case insensitive. + +## @backstage/ui@0.10.0 + +### Minor Changes + +- 16543fa: **Breaking change** The `Cell` component has been refactored to be a generic wrapper component that accepts `children` for custom cell content. The text-specific functionality (previously part of `Cell`) has been moved to a new `CellText` component. + + ### Migration Guide + + If you were using `Cell` with text-specific props (`title`, `description`, `leadingIcon`, `href`), you need to update your code to use `CellText` instead: + + **Before:** + + ```tsx + } + href="/path" + /> + ``` + + **After:** + + ```tsx + } + href="/path" + /> + ``` + + For custom cell content, use the new generic `Cell` component: + + ```tsx + {/* Your custom content */} + ``` + +### Patch Changes + +- 50b7927: Fixed Checkbox indicator showing checkmark color when unchecked. + + Affected components: Checkbox + +- 5bacf55: Fixed `ButtonIcon` incorrectly applying `className` to inner elements instead of only the root element. + + Affected components: ButtonIcon + +- b3ad928: Fixed Table Row component to correctly handle cases where no `href` is provided, preventing unnecessary router provider wrapping and fixing the cursor incorrectly showing as a pointer despite the element not being a link. + + Affected components: Row + +- a20d317: Added row selection support with visual state styling for hover, selected, and pressed states. Fixed checkbox rendering to only show for multi-select toggle mode. + + Affected components: Table, TableHeader, Row, Column + +- fe7c751: Fixed `useTable` hook to prioritize `providedRowCount` over data length for accurate row count in server-side pagination scenarios. + +- c145031: Fixed Table column sorting indicator to show up arrow when no sort is active, correctly indicating that clicking will sort ascending. + + Affected components: Column + +## @backstage/plugin-auth-backend-module-github-provider@0.4.0 + +### Minor Changes + +- b3286d5: Added the `github.com/user-id` annotation to store GitHub's user ID (immutable) in user entities. Also includes addition of the `userIdMatchingUserEntityAnnotation` sign-in resolver that matches users by the new ID. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-catalog-backend@3.3.0 + +### Minor Changes + +- dce1824: Added `ActionsRegistry` actions for `register-entity` and `unregister-entity` + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 8101ec1: Fixed default refresh service to go through the whole ancestry of the entity. +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-openapi-utils@0.6.4 + - @backstage/plugin-events-node@0.4.18 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-github@0.12.0 + +### Minor Changes + +- b3286d5: Added the `github.com/user-id` annotation to store GitHub's user ID (immutable) in user entities. Also includes addition of the `userIdMatchingUserEntityAnnotation` sign-in resolver that matches users by the new ID. + +### Patch Changes + +- ed5a7a3: Introduce new configuration option to exclude suspended users from GitHub Enterprise instances. + + When it’s set to true, suspended users won’t be returned when querying the organization users for GitHub Enterprise instances. + Note that this option should be used only against GitHub Enterprise instances, the property does not exist in the github.com GraphQL schema, setting it will cause a schema validation error and the syncing of users will fail. + +- a413977: Added configurable `pageSizes` option to `GithubOrgEntityProvider` for GitHub GraphQL API queries to prevent `RESOURCE_LIMITS_EXCEEDED` errors with organizations with large number of teams and members. This aligns the configuration options with `GithubMultiOrgEntityProvider`. + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-events-backend-module-kafka@0.2.0 + +### Minor Changes + +- 2c74ea9: Added support for multiple named instances in `kafkaConsumingEventPublisher` configuration. The previous single configuration format is still supported for backward compatibility. +- 2c74ea9: Added `KafkaPublishingEventConsumer` to support sending Backstage events to Kafka topics. + + This addition enables Backstage to publish events to external Kafka systems, complementing the existing ability to receive events from Kafka. This allows for better integration with external systems that rely on Kafka for event streaming. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-kubernetes-backend@0.21.0 + +### Minor Changes + +- 7f9846f: Add possibility to extends Kubernetes REST API. Add fetcher to parameters for custom objects provider + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- fb029b6: Updated luxon types +- e9589d9: Replace `@aws-sdk/signature-v4` with `@smithy/signature-v4`, + as stated in the [package documentation](https://www.npmjs.com/package/@aws-sdk/signature-v4?activeTab=readme) +- 8fa8d87: Add Kubernetes Plugin Secrets Accordion with masked secret datas +- Updated dependencies + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-kubernetes-node@0.4.0 + - @backstage/plugin-kubernetes-common@0.9.9 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-kubernetes-node@0.4.0 + +### Minor Changes + +- 7f9846f: Add possibility to extends Kubernetes REST API. Add fetcher to parameters for custom objects provider + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-kubernetes-common@0.9.9 + +## @backstage/plugin-notifications-backend-module-slack@0.3.0 + +### Minor Changes + +- f95a516: Enables optional routes to Slack channels for broadcast notifications based on origin and/or topics. + +### Patch Changes + +- b80857a: Slack notification handler throttling can now be configured with the `concurrencyLimit` and `throttleInterval` options. +- f8230e4: Updated dependency `@faker-js/faker` to `^10.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-notifications-node@0.2.22 + +## @backstage/plugin-scaffolder@1.35.0 + +### Minor Changes + +- dab3d3f: Added field extension `RepoOwnerPicker` for retrieving GitHub repository owners. + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/plugin-scaffolder-react@1.19.4 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-permission-react@0.4.39 + - @backstage/plugin-scaffolder-common@1.7.4 + - @backstage/plugin-techdocs-react@1.3.6 + +## @backstage/plugin-scaffolder-backend@3.1.0 + +### Minor Changes + +- a4cd405: Add `defaultEnvironment` config to scaffolder to enable more flexible and custom templates. Now it's possible enable access to default parameters and secrets in templates, improving security and reducing complexity. + +### Patch Changes + +- be5972b: Fixed a bug where config was not passed to NunjucksWorkflowRunner, causing defaultEnvironment to be undefined +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 2bae83a: Updated `isolated-vm` to `6.0.1` +- 25b560e: Internal change to support new versions of the `logform` library +- 8f4aded: Fixing OpenAPI definition +- 1226647: Updated dependency `esbuild` to `^0.27.0`. +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-gitlab@0.11.0 + - @backstage/integration@1.19.0 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.5 + - @backstage/backend-defaults@0.14.0 + - @backstage/backend-openapi-utils@0.6.4 + - @backstage/plugin-events-node@0.4.18 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-backend-module-github@0.9.3 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.17 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.16 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.16 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.16 + - @backstage/plugin-scaffolder-common@1.7.4 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.0 + +### Minor Changes + +- 37fba1d: Added support for Bitbucket Cloud OAuth. This introduces an alternative authentication method using a workspace OAuth consumer, alongside App Passwords (deprecated) and API tokens. OAuth does not require a bot or service account and avoids token expiry issues. + + **BREAKING CHANGES** + + - **@backstage/integration** (`src/bitbucketCloud/core.ts`) + + - `getBitbucketCloudRequestOptions` now returns a `Promise` and **must** be awaited. + + - **@backstage/plugin-scaffolder-backend-module-bitbucket-cloud** (`src/actions/helpers.ts`) + - `getBitbucketClient` now returns a `Promise` and **must** be awaited. + - `getAuthorizationHeader` now returns a `Promise` and **must** be awaited. + + **OAuth usage example** + + ```yaml + integrations: + bitbucketCloud: + - clientId: client-id + clientSecret: client-secret + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.5 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.11.0 + +### Minor Changes + +- f2d034b: In the `gitlabRepoPush` action, add 'auto' possibility for `commitAction` input. + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-search-backend-node@1.4.0 + +### Minor Changes + +- 4d3ddb9: Improving method that search tokenizer breaks apart entity names + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/app-defaults@1.7.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/plugin-permission-react@0.4.39 + +## @backstage/backend-dev-utils@0.1.6 + +### Patch Changes + +- 2bae83a: Internal update for Node.js v24 support. + +## @backstage/backend-dynamic-feature-service@0.7.7 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/cli-node@0.2.16 + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-defaults@0.14.0 + - @backstage/backend-openapi-utils@0.6.4 + - @backstage/plugin-app-node@0.1.40 + - @backstage/plugin-catalog-backend@3.3.0 + - @backstage/plugin-events-backend@0.5.9 + - @backstage/plugin-events-node@0.4.18 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/cli-common@0.1.16 + - @backstage/config-loader@1.10.7 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/backend-openapi-utils@0.6.4 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/backend-test-utils@1.10.2 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 8be23a4: Switched `textextensions` dependency for `text-extensions`. +- 5a737e1: Fix PostgreSQL 18 `TestDatabases` by pinning the data directory +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-defaults@0.14.0 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-app-api@1.4.0 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/cli-common@0.1.16 + +### Patch Changes + +- 5cfb2a4: Added new `run`, `runOutput`, and `runCheck` utilities to help run child processes in a safe and portable way. +- c8c2329: Add proxy configuration from env-vars to create-app tasks +- 2bae83a: Bumped dev dependencies `@types/node` + +## @backstage/cli-node@0.2.16 + +### Patch Changes + +- 4e8c726: Updated to use new utilities from `@backstage/cli-common`. +- Updated dependencies + - @backstage/cli-common@0.1.16 + +## @backstage/codemods@0.1.53 + +### Patch Changes + +- 688f070: Updated to use new utilities from `@backstage/cli-common`. +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/cli-common@0.1.16 + +## @backstage/config-loader@1.10.7 + +### Patch Changes + +- 741c47a: Updated dependency `typescript-json-schema` to `^0.67.0`. +- Updated dependencies + - @backstage/cli-common@0.1.16 + +## @backstage/core-app-api@1.19.3 + +### Patch Changes + +- 75683ed: Added replay functionality to `AlertApiForwarder` to buffer and replay recent alerts to new subscribers, preventing missed alerts that were posted before subscription. +- 97cd16f: Internal update of translation imports. +- Updated dependencies + - @backstage/core-plugin-api@1.12.1 + +## @backstage/core-compat-api@0.5.5 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + +## @backstage/core-components@0.18.4 + +### Patch Changes + +- 9a942a4: Fixed bug in the `LogViewer` component where shift + click always opened a new window instead of just changing the selection. + + In addition, improved the `LogViewer` component by a few usability enhancements: + + - Added support for multiple selections using cmd/ctrl + click + - Improved the generated hash that is added to the URL to also support ranges & multiple selections + - Added an hover effect & info tooltip to the "Copy to clipboard" button to indicate its functionality + - Added some color and a separator to the line numbers to improve readability + +- 207c3c8: long words like urls now breaks to new line on warning panels instead of overflowing the container + +- 4c00303: Add `tooltipClasses` prop to `OverflowTooltip` component to allow customisation of the tooltip + +- 5d52dab: Add i18n support for LogViewer search control + +- f6b49ce: added support for wrapLongLines option in CodeSnippet + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + +## @backstage/core-plugin-api@1.12.1 + +### Patch Changes + +- 358c6f7: The `useApp` and `useRouteRef` functions are now forwards compatible with the new frontend system. Along with the previous route reference changes this means that there is no longer a need to use `compatWrapper` from `@backstage/core-compat-api` to make code based on `@backstage/core-plugin-api` compatible with `@backstage/frontend-plugin-api` APIs. +- 97cd16f: Reversed the relationship between the old `@backstage/core-plugin-api` and the new `@backstage/frontend-plugin-api`. Previously, the a lot of API definitions and utilities where defined in the old and re-exported from the old, but this change flips that around so that they now reside in the new package and are re-exported from the old. The external API of both packages remain the same, but this is a step towards being able to add further compatibility with the new frontend system built into the old. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + +## @backstage/create-app@0.7.7 + +### Patch Changes + +- 336db00: Bumped create-app version. +- 2bae83a: Updated engines to support Node 22 or 24 +- c8c2329: Add proxy configuration from env-vars to create-app tasks +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/cli-common@0.1.16 + +## @backstage/dev-utils@1.1.18 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/ui@0.10.0 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/app-defaults@1.7.3 + - @backstage/integration-react@1.2.13 + +## @backstage/frontend-app-api@0.13.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-plugin-api@1.12.1 + - @backstage/frontend-defaults@0.3.4 + +## @backstage/frontend-defaults@0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.3.3 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/frontend-app-api@0.13.3 + +## @backstage/frontend-dynamic-feature-loader@0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + +## @backstage/frontend-plugin-api@0.13.2 + +### Patch Changes + +- 75683ed: Added a new `errorPresentation` prop to `ExtensionBoundary` to control how errors are presented to the user. The default is `'error-display'`, which is the current behavior of showing the error in the `ErrorDisplay` component. The new option is `'error-api'`, posts errors to the `ErrorApi` and does not allow retries. + + The `AppRootElementBlueprint` now wraps its element in an `ErrorBoundary` using the new `'error-api'` presentation mode. + +- 0bc1ce9: Fixed a versioning conflict that could result in a `.withContext` is not a function error. + +- f3f84f1: Made the return type of `.withOverrides` to be simplified. + +- 97cd16f: Reversed the relationship between the old `@backstage/core-plugin-api` and the new `@backstage/frontend-plugin-api`. Previously, the a lot of API definitions and utilities where defined in the old and re-exported from the old, but this change flips that around so that they now reside in the new package and are re-exported from the old. The external API of both packages remain the same, but this is a step towards being able to add further compatibility with the new frontend system built into the old. + +- 9b8bde4: Removed unnecessary dependencies on `@backstage/core-components`, `@backstage/config`, `@material-ui/core`, and `lodash`. + +## @backstage/frontend-test-utils@0.4.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.3.3 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/test-utils@1.7.14 + - @backstage/frontend-app-api@0.13.3 + +## @backstage/integration-react@1.2.13 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/core-plugin-api@1.12.1 + +## @backstage/repo-tools@0.16.1 + +### Patch Changes + +- 688f070: Updated to use new utilities from `@backstage/cli-common`. + +- 85895f9: Updates OpenAPI generator templates to preserve original property names (like 'group-name', 'user-id') from OpenAPI specs when propertyNaming=original is specified. Previously, these were always converted to camelCase regardless of the propertyNaming setting. + + - Updates modelGeneric.mustache templates in both client and server generators + - Updates modelTaggedUnion.mustache templates in both client and server generators + - Uses {{baseName}} when available, falls back to {{name}} for backward compatibility + - Maintains backward compatibility - no changes when propertyNaming=original is not used + +- 2bae83a: Bump `@microsoft/api-documenter` and `@microsoft/api-extractor` to latest versions. + +- d1e38a7: Properly create workspace in OS temporary directory for `generate-patch` command + +- 2bae83a: Bumped dev dependencies `@types/node` + +- Updated dependencies + - @backstage/cli-node@0.2.16 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/cli-common@0.1.16 + - @backstage/config-loader@1.10.7 + +## @techdocs/cli@1.10.3 + +### Patch Changes + +- 43629b1: Updated to use new utilities from `@backstage/cli-common`. +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/backend-defaults@0.14.0 + - @backstage/plugin-techdocs-node@1.13.10 + - @backstage/cli-common@0.1.16 + +## @backstage/test-utils@1.7.14 + +### Patch Changes + +- 97cd16f: Internal update of translation imports. +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/plugin-permission-react@0.4.39 + +## @backstage/theme@0.7.1 + +### Patch Changes + +- fa06f6b: Added a `themeName` prop to `UnifiedThemeProvider`, enabling Backstage UI `data-theme-name` CSS attribute to be set based on active theme. + +## @backstage/plugin-api-docs@0.13.2 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/plugin-catalog@1.32.1 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/plugin-permission-react@0.4.39 + +## @backstage/plugin-app@0.3.3 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- f7bc228: Support to set `defaultLanguage` and `availableLanguages` for the app language API in the new frontend system +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-permission-react@0.4.39 + +## @backstage/plugin-app-backend@0.5.9 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-app-node@0.1.40 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/config-loader@1.10.7 + +## @backstage/plugin-app-node@0.1.40 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/config-loader@1.10.7 + +## @backstage/plugin-app-visualizer@0.1.26 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/ui@0.10.0 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + +## @backstage/plugin-auth@0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/theme@0.7.1 + +## @backstage/plugin-auth-backend@0.25.7 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-auth0-provider@0.2.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-auth-backend@0.25.7 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.15 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.3.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-google-provider@0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-guest-provider@0.2.15 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.4.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-auth-backend@0.25.7 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-okta-provider@0.2.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-openshift-provider@0.1.3 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-node@0.6.10 + +### Patch Changes + +- 2389358: remove leading dot in auth cookie cleanup call +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- b35f8b2: Fixed chunked cookie replacing edge case in OAuthCookieManager class where some of the old chunks would not get removed if new chunked cookie would have fewer chunks. +- e9dd634: fix flawed cookie removal logic with chunked tokens +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-auth-react@0.1.22 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + +## @backstage/plugin-bitbucket-cloud-common@0.3.5 + +### Patch Changes + +- 37fba1d: Added support for Bitbucket Cloud OAuth. This introduces an alternative authentication method using a workspace OAuth consumer, alongside App Passwords (deprecated) and API tokens. OAuth does not require a bot or service account and avoids token expiry issues. + + **BREAKING CHANGES** + + - **@backstage/integration** (`src/bitbucketCloud/core.ts`) + + - `getBitbucketCloudRequestOptions` now returns a `Promise` and **must** be awaited. + + - **@backstage/plugin-scaffolder-backend-module-bitbucket-cloud** (`src/actions/helpers.ts`) + - `getBitbucketClient` now returns a `Promise` and **must** be awaited. + - `getAuthorizationHeader` now returns a `Promise` and **must** be awaited. + + **OAuth usage example** + + ```yaml + integrations: + bitbucketCloud: + - clientId: client-id + clientSecret: client-secret + ``` + +- Updated dependencies + - @backstage/integration@1.19.0 + +## @backstage/plugin-catalog@1.32.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- 91f5ed8: Fixed `catalogAboutEntityCard` to filter icon links before calling useProps(), preventing side effects from hooks in filtered-out links +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-search-react@1.10.1 + - @backstage/core-plugin-api@1.12.1 + - @backstage/core-compat-api@0.5.5 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-permission-react@0.4.39 + - @backstage/plugin-scaffolder-common@1.7.4 + - @backstage/plugin-techdocs-react@1.3.6 + +## @backstage/plugin-catalog-backend-module-aws@0.4.18 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-defaults@0.14.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-kubernetes-common@0.9.9 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-azure@0.3.12 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.4 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.6 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.5 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.6 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-kubernetes-common@0.9.9 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-gitea@0.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.17 + +### Patch Changes + +- ed5a7a3: Introduce new configuration option to exclude suspended users from GitHub Enterprise instances. + + When it’s set to true, suspended users won’t be returned when querying the organization users for GitHub Enterprise instances. + Note that this option should be used only against GitHub Enterprise instances, the property does not exist in the github.com GraphQL schema, setting it will cause a schema validation error and the syncing of users will fail. + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/plugin-catalog-backend-module-github@0.12.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-gitlab@0.7.6 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-defaults@0.14.0 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-backend-module-gitlab@0.7.6 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.7 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- fb029b6: Updated luxon types +- Updated dependencies + - @backstage/backend-defaults@0.14.0 + - @backstage/plugin-catalog-backend@3.3.0 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-ldap@0.12.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-logs@0.1.17 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.3.0 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-catalog-backend-module-msgraph@0.8.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.17 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-scaffolder-common@1.7.4 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.6.7 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.12 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-catalog-graph@0.5.4 + +### Patch Changes + +- a67c144: Fixed zoom out description (replace `&` with &) +- f3f84f1: Minor extension type updates after frontend API bump +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + +## @backstage/plugin-catalog-import@0.13.8 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-permission-react@0.4.39 + +## @backstage/plugin-catalog-node@1.20.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-catalog-react@1.21.4 + +### Patch Changes + +- 6d39141: Fixed an issue where `EntityOwnerPicker` failed to filter options when the input text contained uppercase characters. +- b3c0594: Use a versioned context for `useEntityList`, to better work with mixed `@backstage/plugin-catalog-react` versions. +- c51c901: $contains may have string value in an entity filter. Typescript type and Zod parser were not the same. +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/core-compat-api@0.5.5 + - @backstage/frontend-test-utils@0.4.2 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-permission-react@0.4.39 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.24 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. + +- df4d646: Moved types, API and client to the common package, allowing both frontend and + backend plugins to use the `CatalogUnprocessedEntitiesClient`. + + The following types, clients and interfaces have been deprecated and should be + imported from the `@backstage/plugin-catalog-unprocessed-entities-common` instead: + `CatalogUnprocessedEntitiesApi`, `CatalogUnprocessedEntitiesApiResponse`, `UnprocessedEntity`, + `UnprocessedEntityCache`, `UnprocessedEntityError`, `CatalogUnprocessedEntitiesClient`. + + All those types, clients and interfaces are re-exported temporarily in the + `@backstage/plugin-catalog-unprocessed-entities` package until cleaned up. + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.12 + +## @backstage/plugin-catalog-unprocessed-entities-common@0.0.12 + +### Patch Changes + +- df4d646: Moved types, API and client to the common package, allowing both frontend and + backend plugins to use the `CatalogUnprocessedEntitiesClient`. + + The following types, clients and interfaces have been deprecated and should be + imported from the `@backstage/plugin-catalog-unprocessed-entities-common` instead: + `CatalogUnprocessedEntitiesApi`, `CatalogUnprocessedEntitiesApiResponse`, `UnprocessedEntity`, + `UnprocessedEntityCache`, `UnprocessedEntityError`, `CatalogUnprocessedEntitiesClient`. + + All those types, clients and interfaces are re-exported temporarily in the + `@backstage/plugin-catalog-unprocessed-entities` package until cleaned up. + +## @backstage/plugin-config-schema@0.1.75 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + +## @backstage/plugin-devtools@0.1.34 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- 291bf9d: Added scheduled tasks UI feature for the DevTools plugin +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/plugin-devtools-common@0.1.20 + - @backstage/core-compat-api@0.5.5 + - @backstage/plugin-permission-react@0.4.39 + +## @backstage/plugin-devtools-backend@0.5.12 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 291bf9d: Added scheduled tasks UI feature for the DevTools plugin +- Updated dependencies + - @backstage/backend-defaults@0.14.0 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/cli-common@0.1.16 + - @backstage/config-loader@1.10.7 + - @backstage/plugin-devtools-common@0.1.20 + +## @backstage/plugin-devtools-common@0.1.20 + +### Patch Changes + +- 291bf9d: Added scheduled tasks UI feature for the DevTools plugin + +## @backstage/plugin-events-backend@0.5.9 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.4 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-events-backend-module-aws-sqs@0.4.18 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-events-backend-module-azure@0.2.27 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.27 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-events-backend-module-bitbucket-server@0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-events-backend-module-gerrit@0.2.27 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-events-backend-module-github@0.4.7 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-events-backend-module-gitlab@0.3.8 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-events-backend-module-google-pubsub@0.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-events-backend-test-utils@0.1.51 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + +## @backstage/plugin-events-node@0.4.18 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-gateway-backend@1.1.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 7e860dd: Updated dependency `eventsource` to `^4.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-home@0.8.15 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/plugin-home-react@0.1.33 + - @backstage/theme@0.7.1 + +## @backstage/plugin-home-react@0.1.33 + +### Patch Changes + +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + +## @backstage/plugin-kubernetes@0.12.14 + +### Patch Changes + +- f15d5f1: add missing i18n support for kubernetes and kubernetes-react +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/plugin-kubernetes-react@0.5.14 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-kubernetes-common@0.9.9 + - @backstage/core-plugin-api@1.12.1 + - @backstage/plugin-permission-react@0.4.39 + +## @backstage/plugin-kubernetes-cluster@0.0.32 + +### Patch Changes + +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/plugin-kubernetes-react@0.5.14 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-kubernetes-common@0.9.9 + - @backstage/core-plugin-api@1.12.1 + - @backstage/plugin-permission-react@0.4.39 + +## @backstage/plugin-kubernetes-common@0.9.9 + +### Patch Changes + +- 8fa8d87: Add Kubernetes Plugin Secrets Accordion with masked secret datas + +## @backstage/plugin-kubernetes-react@0.5.14 + +### Patch Changes + +- f966a85: Enabled a pod terminal at GKE +- 8fa8d87: Add Kubernetes Plugin Secrets Accordion with masked secret datas +- f15d5f1: add missing i18n support for kubernetes and kubernetes-react +- Updated dependencies + - @backstage/core-components@0.18.4 + - @backstage/plugin-kubernetes-common@0.9.9 + - @backstage/core-plugin-api@1.12.1 + +## @backstage/plugin-mcp-actions-backend@0.1.6 + +### Patch Changes + +- e83e038: Added `@cfworker/json-schema` as a dependency to this package part of the `@modelcontextprotocol/sdk` bump as it's required in the types +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 79ef471: Clarify error handling in readme and update handleError.ts to include all backstage/errors +- Updated dependencies + - @backstage/backend-defaults@0.14.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-mui-to-bui@0.2.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.10.0 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + +## @backstage/plugin-notifications@0.5.12 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. + +- 53347cc: Move long notification descriptions behind `Show more/less` button. + + This improves readability of the notifications list by preventing long descriptions from taking up too much space + or rendering very small scrollable areas. + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/plugin-signals-react@0.0.18 + +## @backstage/plugin-notifications-backend@0.6.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-signals-node@0.1.27 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-notifications-node@0.2.22 + +## @backstage/plugin-notifications-backend-module-email@0.3.17 + +### Patch Changes + +- a5d5b3a: SES config for the notification email processor now supports utilizing an ARN for the SES identity when sending an email after the SES SDK V2 update. + + The `sesConfig.fromArn` will set the `fromEmailAddressIdentityArn` option for the SES `SendEmailCommand`. The `sesConfig.sourceArn` field is removed since no equivalent option is available in the send email command options. Setting `sesConfig.sourceArn` will have no effect and log a warning. Example changes: + + ```diff + notifications: + processors: + email: + transportConfig: + transport: "ses" + region: "us-west-2" + sender: "sender@mycompany.com" + replyTo: "no-reply@mycompany.com" + sesConfig: + - sourceArn: "arn:aws:ses:us-west-2:123456789012:identity/example.com" + fromArn: "arn:aws:ses:us-west-2:123456789012:identity/example.com" + ``` + +- b267aea: Updated dependency `@types/nodemailer` to `^7.0.0`. + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-notifications-node@0.2.22 + +## @backstage/plugin-notifications-node@0.2.22 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-signals-node@0.1.27 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-org@0.6.47 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + +## @backstage/plugin-org-react@0.1.45 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + +## @backstage/plugin-permission-backend@0.7.7 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-permission-node@0.10.7 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-permission-react@0.4.39 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1 + +## @backstage/plugin-proxy-backend@0.6.9 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-proxy-node@0.1.11 + +## @backstage/plugin-proxy-node@0.1.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.17 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16 + +### Patch Changes + +- 5a6aca2: Improve error message when provided target branch is missing +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.16 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.18 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-defaults@0.14.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-backend-module-github@0.9.3 + +### Patch Changes + +- dab3d3f: Added autocompletion resource `owners` for retrieving GitHub repository owners. +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-notifications-node@0.2.22 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.16 + +### Patch Changes + +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + - @backstage/plugin-scaffolder-node-test-utils@0.3.6 + +## @backstage/plugin-scaffolder-common@1.7.4 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + +## @backstage/plugin-scaffolder-node@0.12.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-common@1.7.4 + +## @backstage/plugin-scaffolder-node-test-utils@0.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-test-utils@1.10.2 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## @backstage/plugin-scaffolder-react@1.19.4 + +### Patch Changes + +- fb029b6: Updated luxon types +- 5ca461e: Fixed bug where custom `review.name` values were incorrectly formatted by `startCase`, preserving them exactly as written. +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- 9b38f22: Updated dependency `use-immer` to `^0.11.0`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/plugin-permission-react@0.4.39 + - @backstage/plugin-scaffolder-common@1.7.4 + +## @backstage/plugin-search@1.5.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-search-react@1.10.1 + - @backstage/core-plugin-api@1.12.1 + +## @backstage/plugin-search-backend@2.0.9 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/backend-defaults@0.14.0 + - @backstage/backend-openapi-utils@0.6.4 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-search-backend-module-catalog@0.3.11 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-search-backend-module-elasticsearch@1.7.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-search-backend-module-explore@0.3.10 + +### Patch Changes + +- 9b69262: Updated dependency `@backstage-community/plugin-explore-common` to `^0.9.0`. +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-search-backend-module-pg@0.5.51 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.16 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-search-backend-module-techdocs@0.4.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/plugin-techdocs-node@1.13.10 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-search-react@1.10.1 + +### Patch Changes + +- 8947a4e: Skip the very first empty search when going to the landing page +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + +## @backstage/plugin-signals@0.0.26 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/plugin-signals-react@0.0.18 + +## @backstage/plugin-signals-backend@0.3.11 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/plugin-signals-node@0.1.27 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-signals-node@0.1.27 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-signals-react@0.0.18 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1 + +## @backstage/plugin-techdocs@1.16.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- 5c33821: Fixed schema type for additionalAllowedURIProtocols +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- 592361e: The `techdocs` config is now marked as optional. +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-search-react@1.10.1 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-auth-react@0.1.22 + - @backstage/plugin-techdocs-react@1.3.6 + +## @backstage/plugin-techdocs-backend@2.1.3 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 2bae83a: Corrected `ErrorCallback` type to work with Node 22 types +- 592361e: The `techdocs` config is now marked as optional. +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-defaults@0.14.0 + - @backstage/plugin-techdocs-node@1.13.10 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.31 + +### Patch Changes + +- 8d6709e: Updated tests to match test-utils change +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-techdocs-react@1.3.6 + +## @backstage/plugin-techdocs-node@1.13.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 703f8c0: There was an issue in the uploading of large size files to the AWS S3. We have modified the logic by adding retry along with multipart uploading functionality. +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + +## @backstage/plugin-techdocs-react@1.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + +## @backstage/plugin-user-settings@0.8.30 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/plugin-signals-react@0.0.18 + +## @backstage/plugin-user-settings-backend@0.3.9 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-defaults@0.14.0 + - @backstage/plugin-signals-node@0.1.27 + - @backstage/backend-plugin-api@1.6.0 + +## example-app@0.2.116 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/cli@0.35.0 + - @backstage/ui@0.10.0 + - @backstage/plugin-catalog-graph@0.5.4 + - @backstage/plugin-api-docs@0.13.2 + - @backstage/plugin-techdocs@1.16.1 + - @backstage/plugin-catalog@1.32.1 + - @backstage/plugin-search@1.5.1 + - @backstage/plugin-org@0.6.47 + - @backstage/plugin-scaffolder-react@1.19.4 + - @backstage/core-components@0.18.4 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.31 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-kubernetes@0.12.14 + - @backstage/plugin-search-react@1.10.1 + - @backstage/core-plugin-api@1.12.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24 + - @backstage/plugin-catalog-import@0.13.8 + - @backstage/plugin-notifications@0.5.12 + - @backstage/plugin-user-settings@0.8.30 + - @backstage/plugin-scaffolder@1.35.0 + - @backstage/plugin-devtools@0.1.34 + - @backstage/plugin-home@0.8.15 + - @backstage/theme@0.7.1 + - @backstage/plugin-kubernetes-cluster@0.0.32 + - @backstage/app-defaults@1.7.3 + - @backstage/frontend-app-api@0.13.3 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-auth-react@0.1.22 + - @backstage/plugin-mui-to-bui@0.2.2 + - @backstage/plugin-permission-react@0.4.39 + - @backstage/plugin-signals@0.0.26 + - @backstage/plugin-techdocs-react@1.3.6 + +## example-app-next@0.0.30 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/cli@0.35.0 + - @backstage/ui@0.10.0 + - @backstage/plugin-catalog-graph@0.5.4 + - @backstage/plugin-api-docs@0.13.2 + - @backstage/plugin-techdocs@1.16.1 + - @backstage/plugin-catalog@1.32.1 + - @backstage/plugin-search@1.5.1 + - @backstage/plugin-app@0.3.3 + - @backstage/plugin-org@0.6.47 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/plugin-scaffolder-react@1.19.4 + - @backstage/core-components@0.18.4 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.31 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-kubernetes@0.12.14 + - @backstage/plugin-search-react@1.10.1 + - @backstage/core-plugin-api@1.12.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24 + - @backstage/plugin-app-visualizer@0.1.26 + - @backstage/plugin-catalog-import@0.13.8 + - @backstage/plugin-notifications@0.5.12 + - @backstage/plugin-user-settings@0.8.30 + - @backstage/plugin-scaffolder@1.35.0 + - @backstage/plugin-home@0.8.15 + - @backstage/theme@0.7.1 + - @backstage/plugin-kubernetes-cluster@0.0.32 + - @backstage/app-defaults@1.7.3 + - @backstage/core-compat-api@0.5.5 + - @backstage/frontend-app-api@0.13.3 + - @backstage/frontend-defaults@0.3.4 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-auth@0.1.3 + - @backstage/plugin-auth-react@0.1.22 + - @backstage/plugin-permission-react@0.4.39 + - @backstage/plugin-signals@0.0.26 + - @backstage/plugin-techdocs-react@1.3.6 + +## app-next-example-plugin@0.0.30 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + +## example-backend@0.0.45 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-mcp-actions-backend@0.1.6 + - @backstage/plugin-scaffolder-backend@3.1.0 + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-defaults@0.14.0 + - @backstage/plugin-app-backend@0.5.9 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.15 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.3 + - @backstage/plugin-auth-backend@0.25.7 + - @backstage/plugin-catalog-backend@3.3.0 + - @backstage/plugin-devtools-backend@0.5.12 + - @backstage/plugin-events-backend@0.5.9 + - @backstage/plugin-kubernetes-backend@0.21.0 + - @backstage/plugin-notifications-backend@0.6.1 + - @backstage/plugin-permission-backend@0.7.7 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/plugin-proxy-backend@0.6.9 + - @backstage/plugin-search-backend@2.0.9 + - @backstage/plugin-signals-backend@0.3.11 + - @backstage/plugin-techdocs-backend@2.1.3 + - @backstage/plugin-auth-backend-module-github-provider@0.4.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-backend-module-github@0.9.3 + - @backstage/plugin-search-backend-module-explore@0.3.10 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.9 + - @backstage/plugin-catalog-backend-module-openapi@0.2.17 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.7 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.7 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.15 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.17 + - @backstage/plugin-search-backend-module-catalog@0.3.11 + - @backstage/plugin-search-backend-module-elasticsearch@1.7.9 + - @backstage/plugin-search-backend-module-techdocs@0.4.9 + +## e2e-test@0.2.35 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.7.7 + - @backstage/cli-common@0.1.16 + +## @internal/frontend@0.0.16 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + +## @internal/scaffolder@0.0.16 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/plugin-scaffolder-react@1.19.4 + +## techdocs-cli-embedded-app@0.2.115 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/cli@0.35.0 + - @backstage/ui@0.10.0 + - @backstage/plugin-techdocs@1.16.1 + - @backstage/plugin-catalog@1.32.1 + - @backstage/core-components@0.18.4 + - @backstage/test-utils@1.7.14 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/app-defaults@1.7.3 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-techdocs-react@1.3.6 + +## yarn-plugin-backstage@0.0.8 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.16 + +## @internal/plugin-todo-list@1.0.46 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + +## @internal/plugin-todo-list-backend@1.0.46 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 diff --git a/docs/releases/v1.46.0-next.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/releases/v1.46.0-next.2-changelog.md b/docs/releases/v1.46.0-next.2-changelog.md new file mode 100644 index 0000000000..0bb2bf687d --- /dev/null +++ b/docs/releases/v1.46.0-next.2-changelog.md @@ -0,0 +1,1897 @@ +# Release v1.46.0-next.2 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.46.0-next.2](https://backstage.github.io/upgrade-helper/?to=1.46.0-next.2) + +## @backstage/backend-plugin-api@1.6.0-next.1 + +### Minor Changes + +- 2a0c4b0: Adds a new experimental `RootSystemMetadataService` for tracking the collection of Backstage instances that may be deployed at any one time. It currently offers a single API, `getInstalledPlugins` that returns a list of installed plugins based on config you have set up in `discovery.endpoints` as well as the plugins installed on the instance you're calling the API with. It does not handle wildcard values or fallback values. The intention is for this plugin to provide plugin authors with a simple interface to fetch a trustworthy list of all installed plugins. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/cli-common@0.1.16-next.2 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/cli@0.35.0-next.2 + +### Minor Changes + +- f8dff94: Switched the default module resolution to `bundler` and the `module` setting to `ES2020`. + + You may need to bump some dependencies as part of this change and fix imports in code. The most common source of this is that type checking will now consider the `exports` field in `package.json` when resolving imports. This in turn can break older versions of packages that had incompatible `exports` fields. Generally these issues will have already been fixed in the upstream packages. + + You might be tempted to use `--skipLibCheck` to hide issues due to this change, but it will weaken the type safety of your project. If you run into a large number of issues and want to keep the old behavior, you can reset the `moduleResolution` and `module` settings your own `tsconfig.json` file to `node` and `ESNext` respectively. But keep in mind that the `node` option will be removed in future versions of TypeScript. + + A future version of Backstage will make these new settings mandatory, as we move to rely on the `exports` field for type resolution in packages, rather than the `typesVersions` field. + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 1226647: Updated dependency `esbuild` to `^0.27.0`. +- f89a074: Updated dependency `@pmmmwh/react-refresh-webpack-plugin` to `^0.6.0`. +- 2b81751: Updated dependency `webpack` to `~5.103.0`. +- fafd9e1: Fixed internal usage of `yargs`. +- 2bae83a: Switched ECMAScript version to ES2023. +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/config-loader@1.10.7-next.1 + - @backstage/cli-common@0.1.16-next.2 + - @backstage/catalog-model@1.7.6 + - @backstage/cli-node@0.2.16-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/eslint-plugin@0.2.0 + - @backstage/release-manifests@0.0.13 + - @backstage/types@1.2.2 + +## @backstage/plugin-kubernetes-backend@0.21.0-next.2 + +### Minor Changes + +- 7f9846f: Add possibility to extends Kubernetes REST API. Add fetcher to parameters for custom objects provider + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- fb029b6: Updated luxon types +- Updated dependencies + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/plugin-kubernetes-node@0.4.0-next.2 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.19 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-kubernetes-node@0.4.0-next.2 + +### Minor Changes + +- 7f9846f: Add possibility to extends Kubernetes REST API. Add fetcher to parameters for custom objects provider + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.11.0-next.1 + +### Minor Changes + +- f2d034b: In the `gitlabRepoPush` action, add 'auto' possibility for `commitAction` input. + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/backend-app-api@1.4.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/backend-defaults@0.14.0-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- aa79251: build(deps): bump `node-forge` from 1.3.1 to 1.3.2 +- f96edff: Allow configuration of the `referrerPolicy` +- fb029b6: Updated luxon types +- 847a330: Fix for `jose` types +- 25b560e: Internal change to support new versions of the `logform` library +- 2a0c4b0: Adds a new experimental `RootSystemMetadataService` for tracking the collection of Backstage instances that may be deployed at any one time. It currently offers a single API, `getInstalledPlugins` that returns a list of installed plugins based on config you have set up in `discovery.endpoints` as well as the plugins installed on the instance you're calling the API with. It does not handle wildcard values or fallback values. The intention is for this plugin to provide plugin authors with a simple interface to fetch a trustworthy list of all installed plugins. +- 3016a79: Updated dependency `@types/archiver` to `^7.0.0`. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config-loader@1.10.7-next.1 + - @backstage/backend-dev-utils@0.1.6-next.0 + - @backstage/backend-app-api@1.4.0-next.1 + - @backstage/cli-node@0.2.16-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.19 + - @backstage/types@1.2.2 + +## @backstage/backend-dev-utils@0.1.6-next.0 + +### Patch Changes + +- 2bae83a: Internal update for Node.js v24 support. + +## @backstage/backend-dynamic-feature-service@0.7.7-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/backend-openapi-utils@0.6.4-next.1 + - @backstage/plugin-app-node@0.1.40-next.1 + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-catalog-backend@3.2.1-next.1 + - @backstage/plugin-events-backend@0.5.9-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config-loader@1.10.7-next.1 + - @backstage/cli-common@0.1.16-next.2 + - @backstage/cli-node@0.2.16-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + - @backstage/plugin-search-backend-node@1.4.0-next.1 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/backend-openapi-utils@0.6.4-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/backend-test-utils@1.10.2-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 8be23a4: Switched `textextensions` dependency for `text-extensions`. +- 5a737e1: Fix PostgreSQL 18 `TestDatabases` by pinning the data directory +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/backend-app-api@1.4.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/cli-common@0.1.16-next.2 + +### Patch Changes + +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/errors@1.2.7 + +## @backstage/codemods@0.1.53-next.2 + +### Patch Changes + +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/cli-common@0.1.16-next.2 + +## @backstage/config-loader@1.10.7-next.1 + +### Patch Changes + +- 741c47a: Updated dependency `typescript-json-schema` to `^0.67.0`. +- Updated dependencies + - @backstage/cli-common@0.1.16-next.2 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/core-app-api@1.19.3-next.1 + +### Patch Changes + +- 75683ed: Added replay functionality to `AlertApiForwarder` to buffer and replay recent alerts to new subscribers, preventing missed alerts that were posted before subscription. +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/core-components@0.18.4-next.2 + +### Patch Changes + +- 4c00303: Add `tooltipClasses` prop to `OverflowTooltip` component to allow customisation of the tooltip +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/theme@0.7.1-next.0 + - @backstage/version-bridge@1.0.11 + +## @backstage/create-app@0.7.7-next.2 + +### Patch Changes + +- 2bae83a: Updated engines to support Node 22 or 24 +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/cli-common@0.1.16-next.2 + +## @backstage/frontend-plugin-api@0.13.2-next.1 + +### Patch Changes + +- 75683ed: Added a new `errorPresentation` prop to `ExtensionBoundary` to control how errors are presented to the user. The default is `'error-display'`, which is the current behavior of showing the error in the `ErrorDisplay` component. The new option is `'error-api'`, posts errors to the `ErrorApi` and does not allow retries. + + The `AppRootElementBlueprint` now wraps its element in an `ErrorBoundary` using the new `'error-api'` presentation mode. + +- f3f84f1: Made the return type of `.withOverrides` to be simplified. + +- Updated dependencies + - @backstage/core-components@0.18.4-next.2 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/integration@1.18.3-next.1 + +### Patch Changes + +- fb029b6: Updated luxon types +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/repo-tools@0.16.1-next.2 + +### Patch Changes + +- 2bae83a: Bump `@microsoft/api-documenter` and `@microsoft/api-extractor` to latest versions. +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config-loader@1.10.7-next.1 + - @backstage/cli-common@0.1.16-next.2 + - @backstage/catalog-model@1.7.6 + - @backstage/cli-node@0.2.16-next.1 + - @backstage/errors@1.2.7 + +## @techdocs/cli@1.10.3-next.2 + +### Patch Changes + +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/plugin-techdocs-node@1.13.10-next.1 + - @backstage/cli-common@0.1.16-next.2 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + +## @backstage/plugin-api-docs@0.13.2-next.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- Updated dependencies + - @backstage/plugin-catalog@1.32.1-next.1 + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-model@1.7.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-react@0.4.39-next.0 + +## @backstage/plugin-app@0.3.3-next.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- f7bc228: Support to set `defaultLanguage` and `availableLanguages` for the app language API in the new frontend system +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/core-components@0.18.4-next.2 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-permission-react@0.4.39-next.0 + +## @backstage/plugin-app-backend@0.5.9-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-app-node@0.1.40-next.1 + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config-loader@1.10.7-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-app-node@0.1.40-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config-loader@1.10.7-next.1 + +## @backstage/plugin-auth-backend@0.25.7-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-auth-backend-module-auth0-provider@0.2.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.7-next.1 + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.15-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-auth-backend-module-github-provider@0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.3.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-auth-backend-module-google-provider@0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-auth-backend-module-guest-provider@0.2.15-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.4.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.7-next.1 + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-auth-backend-module-okta-provider@0.2.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-auth-backend-module-openshift-provider@0.1.3-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + +## @backstage/plugin-auth-node@0.6.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- e9dd634: fix flawed cookie removal logic with chunked tokens +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-catalog@1.32.1-next.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- 91f5ed8: Fixed `catalogAboutEntityCard` to filter icon links before calling useProps(), preventing side effects from hooks in filtered-out links +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-compat-api@0.5.5-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-search-react@1.10.1-next.0 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + +## @backstage/plugin-catalog-backend@3.2.1-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.4-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-catalog-backend-module-aws@0.4.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.19 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + +## @backstage/plugin-catalog-backend-module-azure@0.3.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.4-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-bitbucket-cloud-common@0.3.5-next.0 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.15-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-catalog-backend-module-gitea@0.1.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-catalog-backend-module-github@0.11.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-backend-module-github@0.11.3-next.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab@0.7.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/plugin-catalog-backend-module-gitlab@0.7.6-next.1 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.7-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- fb029b6: Updated luxon types +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/plugin-catalog-backend@3.2.1-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-catalog-backend-module-ldap@0.12.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-catalog-backend-module-logs@0.1.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.2.1-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-catalog-backend-module-msgraph@0.8.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.6.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.12-next.0 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-catalog-graph@0.5.4-next.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/types@1.2.2 + +## @backstage/plugin-catalog-node@1.20.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-catalog-react@1.21.4-next.2 + +### Patch Changes + +- b3c0594: Use a versioned context for `useEntityList`, to better work with mixed `@backstage/plugin-catalog-react` versions. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-compat-api@0.5.5-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/frontend-test-utils@0.4.2-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-permission-react@0.4.39-next.0 + +## @backstage/plugin-devtools-backend@0.5.12-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config-loader@1.10.7-next.1 + - @backstage/cli-common@0.1.16-next.2 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-devtools-common@0.1.19 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-events-backend@0.5.9-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.4-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-events-backend-module-aws-sqs@0.4.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-events-backend-module-azure@0.2.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-events-backend-module-bitbucket-server@0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-events-backend-module-gerrit@0.2.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-events-backend-module-github@0.4.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-events-backend-module-gitlab@0.3.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + +## @backstage/plugin-events-backend-module-google-pubsub@0.1.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-events-backend-module-kafka@0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-events-node@0.4.18-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-gateway-backend@1.1.1-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-home@0.8.15-next.1 + +### Patch Changes + +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- Updated dependencies + - @backstage/core-app-api@1.19.3-next.1 + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/plugin-home-react@0.1.33-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + +## @backstage/plugin-home-react@0.1.33-next.1 + +### Patch Changes + +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/core-components@0.18.4-next.2 + - @backstage/core-plugin-api@1.12.1-next.0 + +## @backstage/plugin-kubernetes-cluster@0.0.32-next.2 + +### Patch Changes + +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-model@1.7.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + - @backstage/plugin-kubernetes-react@0.5.14-next.1 + - @backstage/plugin-permission-react@0.4.39-next.0 + +## @backstage/plugin-mcp-actions-backend@0.1.6-next.1 + +### Patch Changes + +- e83e038: Added `@cfworker/json-schema` as a dependency to this package part of the `@modelcontextprotocol/sdk` bump as it's required in the types +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-notifications-backend@0.6.1-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-signals-node@0.1.27-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/plugin-notifications-node@0.2.22-next.1 + +## @backstage/plugin-notifications-backend-module-email@0.3.17-next.1 + +### Patch Changes + +- b267aea: Updated dependency `@types/nodemailer` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/plugin-notifications-node@0.2.22-next.1 + +## @backstage/plugin-notifications-backend-module-slack@0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/plugin-notifications-node@0.2.22-next.1 + +## @backstage/plugin-notifications-node@0.2.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-signals-node@0.1.27-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-notifications-common@0.2.0 + +## @backstage/plugin-org@0.6.47-next.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-model@1.7.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-common@1.1.7 + +## @backstage/plugin-permission-backend@0.7.7-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-permission-node@0.10.7-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.3 + +## @backstage/plugin-proxy-backend@0.6.9-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/types@1.2.2 + - @backstage/plugin-proxy-node@0.1.11-next.1 + +## @backstage/plugin-proxy-node@0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + +## @backstage/plugin-scaffolder@1.34.4-next.1 + +### Patch Changes + +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-scaffolder-react@1.19.4-next.2 + - @backstage/integration@1.18.3-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + +## @backstage/plugin-scaffolder-backend@3.1.0-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 2bae83a: Updated `isolated-vm` to `6.0.1` +- 25b560e: Internal change to support new versions of the `logform` library +- 1226647: Updated dependency `esbuild` to `^0.27.0`. +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-gitlab@0.11.0-next.1 + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/backend-openapi-utils@0.6.4-next.1 + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-bitbucket-cloud-common@0.3.5-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15-next.1 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.16-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.17-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.16-next.1 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.16-next.1 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.16-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.9.3-next.1 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.16-next.1 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-bitbucket-cloud-common@0.3.5-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16-next.1 + +### Patch Changes + +- 5a6aca2: Improve error message when provided target branch is missing +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/plugin-scaffolder-backend-module-github@0.9.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/plugin-notifications-node@0.2.22-next.1 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.16-next.1 + +### Patch Changes + +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + - @backstage/plugin-scaffolder-node-test-utils@0.3.6-next.1 + +## @backstage/plugin-scaffolder-node@0.12.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + +## @backstage/plugin-scaffolder-node-test-utils@0.3.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-test-utils@1.10.2-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## @backstage/plugin-scaffolder-react@1.19.4-next.2 + +### Patch Changes + +- fb029b6: Updated luxon types +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- 9b38f22: Updated dependency `use-immer` to `^0.11.0`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + +## @backstage/plugin-search@1.5.1-next.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-search-react@1.10.1-next.0 + +## @backstage/plugin-search-backend@2.0.9-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/backend-openapi-utils@0.6.4-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-backend-node@1.4.0-next.1 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-catalog@0.3.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-backend-node@1.4.0-next.1 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-elasticsearch@1.7.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/plugin-search-backend-node@1.4.0-next.1 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-explore@0.3.10-next.1 + +### Patch Changes + +- 9b69262: Updated dependency `@backstage-community/plugin-explore-common` to `^0.9.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/plugin-search-backend-node@1.4.0-next.1 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-pg@0.5.51-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/plugin-search-backend-node@1.4.0-next.1 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/plugin-search-backend-node@1.4.0-next.1 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-module-techdocs@0.4.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.13.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-backend-node@1.4.0-next.1 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-search-backend-node@1.4.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-common@1.2.21 + +## @backstage/plugin-signals-backend@0.3.11-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/plugin-signals-node@0.1.27-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-signals-node@0.1.27-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## @backstage/plugin-techdocs@1.16.1-next.2 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/plugin-auth-react@0.1.22-next.0 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-search-react@1.10.1-next.0 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + +## @backstage/plugin-techdocs-backend@2.1.3-next.2 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 2bae83a: Corrected `ErrorCallback` type to work with Node 22 types +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/plugin-techdocs-node@1.13.10-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## @backstage/plugin-techdocs-node@1.13.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 703f8c0: There was an issue in the uploading of large size files to the AWS S3. We have modified the logic by adding retry along with multipart uploading functionality. +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.19 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-user-settings-backend@0.3.9-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-signals-node@0.1.27-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-user-settings-common@0.0.1 + +## example-app@0.2.116-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3-next.1 + - @backstage/cli@0.35.0-next.2 + - @backstage/plugin-catalog-graph@0.5.4-next.1 + - @backstage/plugin-api-docs@0.13.2-next.1 + - @backstage/plugin-techdocs@1.16.1-next.2 + - @backstage/plugin-catalog@1.32.1-next.1 + - @backstage/plugin-search@1.5.1-next.1 + - @backstage/plugin-org@0.6.47-next.1 + - @backstage/plugin-scaffolder-react@1.19.4-next.2 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/plugin-home@0.8.15-next.1 + - @backstage/plugin-scaffolder@1.34.4-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.32-next.2 + - @backstage/app-defaults@1.7.3-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/frontend-app-api@0.13.3-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/ui@0.10.0-next.1 + - @backstage/plugin-auth-react@0.1.22-next.0 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-import@0.13.8-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24-next.1 + - @backstage/plugin-devtools@0.1.34-next.0 + - @backstage/plugin-kubernetes@0.12.14-next.1 + - @backstage/plugin-mui-to-bui@0.2.2-next.1 + - @backstage/plugin-notifications@0.5.12-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-search-react@1.10.1-next.0 + - @backstage/plugin-signals@0.0.26-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.31-next.1 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/plugin-user-settings@0.8.30-next.0 + +## example-app-next@0.0.30-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3-next.1 + - @backstage/cli@0.35.0-next.2 + - @backstage/plugin-catalog-graph@0.5.4-next.1 + - @backstage/plugin-api-docs@0.13.2-next.1 + - @backstage/plugin-techdocs@1.16.1-next.2 + - @backstage/plugin-catalog@1.32.1-next.1 + - @backstage/plugin-search@1.5.1-next.1 + - @backstage/plugin-app@0.3.3-next.1 + - @backstage/plugin-org@0.6.47-next.1 + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-scaffolder-react@1.19.4-next.2 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/plugin-home@0.8.15-next.1 + - @backstage/plugin-scaffolder@1.34.4-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.32-next.2 + - @backstage/app-defaults@1.7.3-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-compat-api@0.5.5-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/frontend-app-api@0.13.3-next.0 + - @backstage/frontend-defaults@0.3.4-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/ui@0.10.0-next.1 + - @backstage/plugin-app-visualizer@0.1.26-next.1 + - @backstage/plugin-auth@0.1.3-next.0 + - @backstage/plugin-auth-react@0.1.22-next.0 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-import@0.13.8-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24-next.1 + - @backstage/plugin-kubernetes@0.12.14-next.1 + - @backstage/plugin-notifications@0.5.12-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-search-react@1.10.1-next.0 + - @backstage/plugin-signals@0.0.26-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.31-next.1 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/plugin-user-settings@0.8.30-next.0 + +## example-backend@0.0.45-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-mcp-actions-backend@0.1.6-next.1 + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/plugin-app-backend@0.5.9-next.1 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.15-next.1 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.3-next.1 + - @backstage/plugin-auth-backend@0.25.7-next.1 + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-catalog-backend@3.2.1-next.1 + - @backstage/plugin-devtools-backend@0.5.12-next.1 + - @backstage/plugin-events-backend@0.5.9-next.1 + - @backstage/plugin-kubernetes-backend@0.21.0-next.2 + - @backstage/plugin-notifications-backend@0.6.1-next.1 + - @backstage/plugin-permission-backend@0.7.7-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/plugin-proxy-backend@0.6.9-next.1 + - @backstage/plugin-scaffolder-backend@3.1.0-next.1 + - @backstage/plugin-search-backend@2.0.9-next.1 + - @backstage/plugin-signals-backend@0.3.11-next.1 + - @backstage/plugin-techdocs-backend@2.1.3-next.2 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/plugin-search-backend-module-explore@0.3.10-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-auth-backend-module-github-provider@0.3.10-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.9-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.2.17-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.7-next.2 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.7-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.15-next.1 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-scaffolder-backend-module-github@0.9.3-next.1 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.17-next.1 + - @backstage/plugin-search-backend-module-catalog@0.3.11-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.7.9-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.4.9-next.1 + - @backstage/plugin-search-backend-node@1.4.0-next.1 + +## techdocs-cli-embedded-app@0.2.115-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3-next.1 + - @backstage/cli@0.35.0-next.2 + - @backstage/plugin-techdocs@1.16.1-next.2 + - @backstage/plugin-catalog@1.32.1-next.1 + - @backstage/core-components@0.18.4-next.2 + - @backstage/app-defaults@1.7.3-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/test-utils@1.7.14-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/ui@0.10.0-next.1 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + +## @internal/plugin-todo-list-backend@1.0.46-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/errors@1.2.7 diff --git a/docs/releases/v1.46.0.md b/docs/releases/v1.46.0.md new file mode 100644 index 0000000000..9ee4c68681 --- /dev/null +++ b/docs/releases/v1.46.0.md @@ -0,0 +1,129 @@ +--- +id: v1.46.0 +title: v1.46.0 +description: Backstage Release v1.46.0 +--- + +These are the release notes for the v1.46.0 release of [Backstage](https://backstage.io/). + +A huge thanks to the whole team of maintainers and contributors as well as the amazing Backstage Community for the hard work in getting this release developed and done. + +## Highlights + +To begin with, this release has a few important framework level updates. + +### **BREAKING:** Updated supported Node.js versions to 22 and 24 + +This release moves up the supported Node.js versions to 22 and 24, according to our [versioning policy](https://backstage.io/docs/overview/versioning-policy#nodejs-releases). As part of this change, we are also setting the compilation targets to ES2023, which lines up with the supported Node.js versions. + +The default TypeScript `target` compiler option is however left at ES2022. This is because older versions of TypeScript will error if they see an unsupported value, even if an override is set in your local `tsconfig.json`. You can still choose to set the TypeScript `target` to either `ES2023` or even `ES2024` in your own `tsconfig.json` if you prefer. + +### **BREAKING:** Switched modules to `ES2020` and resolution mode to `bundler` + +Now that the ecosystem has had some time to mature and adapt, we have finally switched over the default module resolution mode to `bundler` instead of `node`. The `module` setting was also changed to `ES2022`. + +You _may_ need to bump some dependencies as part of this change, or fix imports in code. The most common source of this is that type checking will now consider the `exports` field in `package.json` when resolving imports. This in turn can break older versions of packages that had incompatible `exports` fields. Generally these issues will have already been fixed in the upstream packages, but if you depend on old versions or unmaintained packages, you may still notice some effects of this. + +You might be tempted to use `--skipLibCheck` to hide issues due to this change, but it will weaken the type safety of your project. If you run into a large number of issues and want to keep the old behavior, you can reset the `moduleResolution` and `module` settings of your own `tsconfig.json` file to `node` and `ESNext` respectively. But keep in mind that the `node` option will be removed in future versions of TypeScript. + +A future version of Backstage will make these new settings mandatory, as we move to rely on the `exports` field for type resolution in packages, rather than the `typesVersions` field. + +### **BREAKING:** Moving toward updating `jest` and its DOM + +The `jest` test framework used to be a builtin dependency of the Backstage CLI. In this release, it has been made into a peer dependency to allow for easy upgrades to newer versions as you see fit. + +If you run tests using the Backstage CLI, you must therefore add Jest and its environment dependencies as `devDependencies` in your project root `package.json` when you upgrade. + +You can choose to install either Jest 29 or Jest 30. The built-in Jest version before this change was Jest 29, however, we recommend that you switch to Jest 30. Upgrading will solve the `Could not parse CSS stylesheet` errors, allow you to use MSW v2 in web packages, and ensure that you remain compatible with future versions of the Backstage CLI. Support for Jest 29 is temporary, with the purpose of allowing you to upgrade at your own pace, but it will eventually be removed. + +- **Jest 29**: Install `jest@^29` and `jest-environment-jsdom@^29` in your root `package.json`. No migration needed, but you may see `Could not parse CSS stylesheet` warnings/errors when testing components from `@backstage/ui` or other packages using CSS `@layer` declarations. +- **Jest 30**: Install `jest@^30`, `@types/jest@^30`, `@jest/environment-jsdom-abstract@^30`, and `jsdom@^27` in your root `package.json`. Fixes the stylesheet parsing warnings/errors, but requires migration steps. + +See the [Jest 30 migration guide](https://backstage.io/docs/tutorials/jest30-migration) for detailed migration instructions. + +### **BREAKING:** Now using correct configuration options for Valkey + +If you are using Valkey as your backend cache provider, take note. It used to piggyback on the Redis configuration format, but that was not quite correct. So its config options have been updated to better match its capabilities. + +So if you use Valkey, you need to upgrade your `app-config.yaml`: + +```diff +backend: + cache: + store: valkey + connection: ... + client: +- namespace: 'my-app' +- keyPrefixSeparator: ':' ++ keyPrefix: 'my-app:' +- clearBatchSize: 1000 +- useUnlink: false +``` + +Contributed by [@benjidotsh](https://github.com/benjidotsh) in [#31497](https://github.com/backstage/backstage/pull/31497) + +### **BREAKING:** Techdocs addon test utils removed explicit `screen` + +`TechdocsAddonTester.renderWithEffects()` used to return a `screen` that you could use for things like `getByText`. In order to support moving to newer versions of `@testing-library`, we have removed this, and you should import and use the normal `screen` from `@testing-library/react` instead. + +However: that `screen` can’t see inside the shadow DOM, which TechDocs uses. So if your tests need to look inside shadow DOM, you must install `shadow-dom-testing-library` and use its `screen` which provides special shadow-DOM queries, such as `getByShadowText` instead of `getByText. + +Example: + +```tsx +import { screen } from 'shadow-dom-testing-library'; +// ... render the addon ... +await TechDocsAddonTester.buildAddonsInTechDocs([]) + .withDom(TEST_CONTENT) + .renderWithEffects(); +expect(screen.getByShadowText('TEST_CONTENT')).toBeInTheDocument(); +``` + +### **BREAKING:** Backstage-UI advances! + +Again, Backstage-UI has a bunch of updates! One of them is breaking: If you were using the `Cell` component with text-specific props (`title` etc), you should now instead use the `CellText` component for that. Plain `Cell` is now meant to be used with generic child elements instead. + +### Simplified route compatibility + +If you are partway through a migration to the New Frontend System, you may enjoy the fact that the `useApp` and `useRouteRef` hooks now are forwards compatible with NFS. Along with the previous route reference changes this means that there is no longer a need to use `compatWrapper` from `@backstage/core-compat-api` to make code based on `@backstage/core-plugin-api` compatible with `@backstage/frontend-plugin-api` APIs. + +### Support for Bitbucket Cloud OAuth + +Support for Bitbucket Cloud OAuth has been added - which was much welcome. This introduces an alternative authentication method using a workspace OAuth consumer, alongside App Passwords (deprecated) and API tokens. OAuth does not require a bot or service account and avoids token expiry issues. + +As part of this change, some breaking changes were made to low level auth getter functions, such that they now return a promise instead of a plain value. + +Contributed by [@jksmth](https://github.com/jksmth) in [#31848](https://github.com/backstage/backstage/pull/31848) + +### New Catalog actions + +There are two new MCP-usable actions on the block for registering and unregistering entities: `catalog:register-entity` and `catalog:unregister-entity`. + +Contributed by [@gabemontero](https://github.com/gabemontero) in [#32042](https://github.com/backstage/backstage/pull/32042) + +### New `RepoOwnerPicker` for Scaffolder + +A new field extension for selecting an organization or owner for a particular repository has been shipped! Currently, this only supports GitHub autocomplete, but contributions welcome in making this available for other providers! + +Contributed by [@benjidotsh](https://github.com/benjidotsh) in [#32105](https://github.com/backstage/backstage/pull/32105) + +## Security Fixes + +This release does not contain any security fixes. + +## Upgrade path + +We recommend that you keep your Backstage project up to date with this latest release. For more guidance on how to upgrade, check out the documentation for [keeping Backstage updated](https://backstage.io/docs/getting-started/keeping-backstage-updated). + +## Links and References + +Below you can find a list of links and references to help you learn about and start using this new release. + +- [Backstage official website](https://backstage.io/), [documentation](https://backstage.io/docs/), and [getting started guide](https://backstage.io/docs/getting-started/) +- [GitHub repository](https://github.com/backstage/backstage) +- Backstage's [versioning and support policy](https://backstage.io/docs/overview/versioning-policy) +- [Community Discord](https://discord.gg/backstage-687207715902193673) for discussions and support +- [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.46.0-changelog.md) +- Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) + +Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/tooling/cli/02-build-system.md b/docs/tooling/cli/02-build-system.md index c8d9034955..6f1b7fd54a 100644 --- a/docs/tooling/cli/02-build-system.md +++ b/docs/tooling/cli/02-build-system.md @@ -610,7 +610,7 @@ With that in mind, here are some IDEs configurations to run backstage components 1. Click on "Edit Configurations" on top panel 2. In the modal dialog click on link "Edit configuration templates..." located in the bottom left corner. 3. "Configuration file": leave empty (`backstage-cli` adds the config) - 4. "Node options": `--no-node-snapshot --experimental-vm-modules` + 4. "Node options": ` --experimental-vm-modules` 5. "Jest package": `~/workspace/backstage/node_modules/@backstage/cli` - the location of the backstage cli package. 6. "Working directory": `~/workspace/backstage` 7. "Jest Options": `repo test --runInBand --watch=false` @@ -621,20 +621,21 @@ With that in mind, here are some IDEs configurations to run backstage components #### VS Code +1. Install the [Jest extension](https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest) for VS Code. +2. Update `settings.json` in the `.vscode` folder with: + ```jsonc { "jest.jestCommandLine": "yarn test", // In a large repo like the Backstage main repo you likely want to disable // watch mode and the initial test run too, leaving just manual and perhaps // on-save test runs in place. - "jest.autoRun": { - "watch": false, - "onSave": "test-src-file" - } + "jest.runMode": "on-save" } ``` -A complete launch configuration for VS Code debugging may look like this: +3. Add a launch configuration for VS Code in `launch.json` in the `.vscode` folder. + A complete configuration for debugging may look like this: ```jsonc { @@ -653,11 +654,13 @@ A complete launch configuration for VS Code debugging may look like this: ], "console": "integratedTerminal", "internalConsoleOptions": "neverOpen", - "disableOptimisticBPs": true, - "program": "${workspaceFolder}/node_modules/.bin/backstage-cli" + "program": "${workspaceFolder}/node_modules/@backstage/cli/bin/backstage-cli" } ``` +4. The configuration is not for manual runs from the "Run and Debug" view. + Instead use the Jest test explorer or the [test's gutter menu](https://github.com/jest-community/vscode-jest#how-to-trigger-a-test-run). + ## Publishing Package publishing is an optional part of the Backstage build system and not diff --git a/docs/tutorials/jest30-migration.md b/docs/tutorials/jest30-migration.md new file mode 100644 index 0000000000..ea79b03009 --- /dev/null +++ b/docs/tutorials/jest30-migration.md @@ -0,0 +1,95 @@ +--- +id: jest30-migration +title: Migrating to Jest 30 +description: A guide to migrating your project to Jest 30 and JSDOM 27 +--- + +Starting with a recent version of `@backstage/cli`, `jest` is a peer dependency. If you run tests using Backstage CLI, you must add Jest and its environment dependencies as `devDependencies` in your project. + +You can choose to install either Jest 29 or Jest 30. The built-in Jest version before this change was Jest 29, however, we recommend that you switch to Jest 30. Upgrading will solve the `Could not parse CSS stylesheet` errors, allow you to use MSW v2 in web packages, and ensure that you remain compatible with future versions of the Backstage CLI. Support for Jest 29 is temporary, with the purpose of allowing you to upgrade at your own pace, but it will eventually be removed. + +- **Jest 29**: + + ```bash + # in your repository root, run: + yarn add --dev jest@^29 @types/jest@^29 jest-environment-jsdom@^29 + ``` + + Pros: No migration needed + + Cons: You may see `Could not parse CSS stylesheet` warnings/errors when testing components from `@backstage/ui` or other packages using CSS `@layer` declarations + +- **Jest 30**: + + ```bash + # in your repository root, run: + yarn add --dev jest@^30 @types/jest@^30 @jest/environment-jsdom-abstract@^30 jsdom@^27 + ``` + + Pros: Fixes the stylesheet parsing warnings/errors + + Cons: Requires migration steps (see below) in your tests + +## Migration Guide + +The examples below are issues we encountered while migrating the Backstage repository. For a complete list of breaking changes, see the official documentation: + +- [Jest 30 upgrade guide](https://jestjs.io/docs/upgrading-to-jest30) +- [JSDOM changelog](https://github.com/jsdom/jsdom/releases) + +### Jest 30 + +**Asymmetric matchers with arrays**: `expect.objectContaining()` no longer works with arrays. + +```diff +- expect(result).toEqual(expect.objectContaining([{ id: '123' }])); ++ expect(result).toEqual([{ id: '123' }]); +// or ++ expect(result).toEqual(expect.arrayContaining([{ id: '123' }])); +``` + +**Array length assertions**: `expect.objectContaining({ length: N })` no longer works. + +```diff +- expect(fn).toHaveBeenCalledWith(expect.objectContaining({ length: 2 })); ++ expect(fn).toHaveBeenCalledWith(expect.any(Array)); ++ expect(fn.mock.calls[0][0]).toHaveLength(2); +``` + +**Deprecated matcher aliases removed**: Replace with canonical names. + +```diff +- expect(fn).toBeCalled(); ++ expect(fn).toHaveBeenCalled(); +``` + +**Snapshots**: Regenerate snapshots as the header format has changed. + +```bash +yarn test --no-watch -u +``` + +### JSDOM 27 + +**window.location is non-configurable**: You can no longer mock location via `Object.defineProperty`. + +```diff +- Object.defineProperty(window, 'location', { value: { href: '' } }); ++ // Option 1: Use history API ++ history.replaceState({}, '', '/new-path'); ++ // Option 2: Spy on navigation methods ++ const spy = jest.spyOn(component, 'navigate'); +``` + +**CSS color values**: Colors may be returned as RGB instead of named colors. + +```diff +- expect(element.style.color).toBe('red'); ++ expect(element.style.color).toBe('rgb(255, 0, 0)'); +``` + +**Error format changes**: Error messages and stack traces may have different formatting. + +#### If you run into `Cannot read properties of null (reading 'constructor')` + +Certain Backstage UI-components (e.g. Button) have a combination of CSS that triggers this error in tests. The solution is to make sure you have at least v0.9.25 of `@acemir/cssom`. diff --git a/docs/tutorials/using-backstage-proxy-within-plugin.md b/docs/tutorials/using-backstage-proxy-within-plugin.md index 58e973d891..bb416bf731 100644 --- a/docs/tutorials/using-backstage-proxy-within-plugin.md +++ b/docs/tutorials/using-backstage-proxy-within-plugin.md @@ -94,15 +94,15 @@ export const AwesomeUsersTable = () => { This section describes the steps to wrap your API client in a [Utility API](../api/utility-apis.md), which are: -- use [`createApiRef`](../reference/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`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.createApiRef.html) to create a + new [`ApiRef`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiRef.html) +- register an [`ApiFactory`](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.ApiFactory.html) with your plugin using - [`createApiFactory`](../reference/core-plugin-api.createapifactory.md). This + [`createApiFactory`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.createApiFactory.html). This will wrap your API implementation, associate your `ApiRef` with your implementation and tell backstage how to instantiate it - finally, you can use your API in your components by calling - [`useApi`](../reference/core-plugin-api.useapi.md) + [`useApi`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.useApi.html) ### Defining the API client interface @@ -187,8 +187,8 @@ export class MyAwesomeApiClient implements MyAwesomeApi { ``` > Check out the docs for more information on the -> [DiscoveryApi](../reference/core-plugin-api.discoveryapi.md) or the -> [FetchApi](../reference/core-plugin-api.fetchapi.md) +> [DiscoveryApi](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.DiscoveryApi.html) or the +> [FetchApi](https://backstage.io/api/stable/types/_backstage_frontend-plugin-api.FetchApi.html) ### Bundling your ApiRef with your plugin @@ -233,7 +233,7 @@ export const myCustomPlugin = createPlugin({ ### Using the API in your components Now you should be able to access your API using the backstage hook -[`useApi`](../reference/core-plugin-api.useapi.md) from within your plugin code. +[`useApi`](https://backstage.io/api/stable/functions/_backstage_frontend-plugin-api.useApi.html) from within your plugin code. ```ts title="plugins/my-awesome-plugin/src/components/AwesomeUsersTable.tsx" import { useApi } from '@backstage/core-plugin-api'; diff --git a/microsite/blog/2025-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/airbrake.yaml b/microsite/data/plugins/airbrake.yaml index 8facf13185..59910df629 100644 --- a/microsite/data/plugins/airbrake.yaml +++ b/microsite/data/plugins/airbrake.yaml @@ -6,5 +6,5 @@ category: Monitoring description: Access Airbrake error monitoring and other integrations from within Backstage documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/airbrake/plugins/airbrake iconUrl: https://wp-assets.airbrake.io/wp-content/uploads/2020/10/05222904/Square-white-A-on-Orange.png -npmPackageName: '@backstage/plugin-airbrake' +npmPackageName: '@backstage-community/plugin-airbrake' addedDate: '2022-01-10' diff --git a/microsite/data/plugins/analytics-module-ga.yaml b/microsite/data/plugins/analytics-module-ga.yaml index 3dcb27bcd5..537a360c8c 100644 --- a/microsite/data/plugins/analytics-module-ga.yaml +++ b/microsite/data/plugins/analytics-module-ga.yaml @@ -6,5 +6,5 @@ category: Monitoring description: Track usage of your Backstage instance using Google Analytics 4. documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/analytics/plugins/analytics-module-ga4#readme iconUrl: /img/ga-icon.png -npmPackageName: '@backstage/plugin-analytics-module-ga4' +npmPackageName: '@backstage-community/plugin-analytics-module-ga4' addedDate: '2021-10-07' diff --git a/microsite/data/plugins/apollo-explorer.yaml b/microsite/data/plugins/apollo-explorer.yaml index 2b0f834f92..83d121e055 100644 --- a/microsite/data/plugins/apollo-explorer.yaml +++ b/microsite/data/plugins/apollo-explorer.yaml @@ -6,5 +6,5 @@ category: Debugging description: Integrates Apollo Explorer graphs as a tool to browse GraphQL API endpoints inside Backstage. documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/apollo-explorer/plugins/apollo-explorer/README.md iconUrl: /img/apollo-explorer.png -npmPackageName: '@backstage/plugin-apollo-explorer' +npmPackageName: '@backstage-community/plugin-apollo-explorer' addedDate: '2022-07-20' diff --git a/microsite/data/plugins/aws-amazon-ecs.yaml b/microsite/data/plugins/aws-amazon-ecs.yaml index a94779babd..e16028d802 100644 --- a/microsite/data/plugins/aws-amazon-ecs.yaml +++ b/microsite/data/plugins/aws-amazon-ecs.yaml @@ -6,5 +6,5 @@ category: Infrastructure description: View resources related to Amazon Elastic Container Service for your components in Backstage. documentation: https://github.com/awslabs/backstage-plugins-for-aws/tree/main/plugins/ecs#readme iconUrl: https://raw.githubusercontent.com/awslabs/backstage-plugins-for-aws/main/docs/images/logos/amazon-ecs-logo.png -npmPackageName: 'https://www.npmjs.com/package/@aws/amazon-ecs-plugin-for-backstage' +npmPackageName: '@aws/amazon-ecs-plugin-for-backstage' addedDate: '2024-04-22' diff --git a/microsite/data/plugins/aws-codebuild.yaml b/microsite/data/plugins/aws-codebuild.yaml index 2c12f92dce..504730402f 100644 --- a/microsite/data/plugins/aws-codebuild.yaml +++ b/microsite/data/plugins/aws-codebuild.yaml @@ -6,5 +6,5 @@ category: CI/CD description: View resources related to AWS CodeBuild for your components in Backstage. documentation: https://github.com/awslabs/backstage-plugins-for-aws/tree/main/plugins/codebuild#readme iconUrl: https://raw.githubusercontent.com/awslabs/backstage-plugins-for-aws/main/docs/images/logos/aws-codebuild-logo.png -npmPackageName: 'https://www.npmjs.com/package/@aws/aws-codebuild-plugin-for-backstage' +npmPackageName: '@aws/aws-codebuild-plugin-for-backstage' addedDate: '2024-04-22' diff --git a/microsite/data/plugins/aws-codepipeline.yaml b/microsite/data/plugins/aws-codepipeline.yaml index 16986b6718..a2eb8de230 100644 --- a/microsite/data/plugins/aws-codepipeline.yaml +++ b/microsite/data/plugins/aws-codepipeline.yaml @@ -6,5 +6,5 @@ category: CI/CD description: View resources related to AWS CodePipeline for your components in Backstage. documentation: https://github.com/awslabs/backstage-plugins-for-aws/tree/main/plugins/codepipeline#readme iconUrl: https://raw.githubusercontent.com/awslabs/backstage-plugins-for-aws/main/docs/images/logos/aws-codepipeline-logo.png -npmPackageName: 'https://www.npmjs.com/package/@aws/aws-codepipeline-plugin-for-backstage' +npmPackageName: '@aws/aws-codepipeline-plugin-for-backstage' addedDate: '2024-04-22' diff --git a/microsite/data/plugins/azure-pipelines.yaml b/microsite/data/plugins/azure-pipelines.yaml index e0e3534fdb..f9536eb192 100644 --- a/microsite/data/plugins/azure-pipelines.yaml +++ b/microsite/data/plugins/azure-pipelines.yaml @@ -6,5 +6,5 @@ category: CI/CD description: Easily view your Azure Pipelines within the Software Catalog documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/azure-devops/plugins/azure-devops/README.md iconUrl: /img/azure-pipelines.svg -npmPackageName: '@backstage/plugin-azure-devops' +npmPackageName: '@backstage-community/plugin-azure-devops' addedDate: '2021-12-22' diff --git a/microsite/data/plugins/badges.yaml b/microsite/data/plugins/badges.yaml index 789aaf155d..9452054810 100644 --- a/microsite/data/plugins/badges.yaml +++ b/microsite/data/plugins/badges.yaml @@ -6,5 +6,5 @@ category: Discovery description: The badges plugin offers a set of badges that can be used outside of Backstage, showing information related to data from the catalog. documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/badges/plugins/badges/README.md iconUrl: /img/badges.svg -npmPackageName: '@backstage/plugin-badges' +npmPackageName: '@backstage-community/plugin-badges' addedDate: '2021-09-29' diff --git a/microsite/data/plugins/bazaar.yaml b/microsite/data/plugins/bazaar.yaml index 1130822e77..d14aaaecd8 100644 --- a/microsite/data/plugins/bazaar.yaml +++ b/microsite/data/plugins/bazaar.yaml @@ -5,5 +5,5 @@ category: Discovery description: A marketplace where engineers can propose projects suitable for inner sourcing documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/bazaar/plugins/bazaar/README.md iconUrl: /img/bazaar.svg -npmPackageName: '@backstage/plugin-bazaar' +npmPackageName: '@backstage-community/plugin-bazaar' addedDate: '2022-01-11' diff --git a/microsite/data/plugins/bitrise.yaml b/microsite/data/plugins/bitrise.yaml index 5cbfe5aabf..31f50a007c 100644 --- a/microsite/data/plugins/bitrise.yaml +++ b/microsite/data/plugins/bitrise.yaml @@ -6,5 +6,5 @@ category: CI/CD description: View Bitrise builds and download the build artifacts within Backstage. documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/bitrise/plugins/bitrise/README.md iconUrl: https://avatars.githubusercontent.com/u/7174390?s=400&v=4 -npmPackageName: '@backstage/plugin-bitrise' +npmPackageName: '@backstage-community/plugin-bitrise' addedDate: '2021-03-01' 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/digital.ai-deploy.yaml b/microsite/data/plugins/digital.ai-deploy.yaml index a36dd08ffb..62203a99ea 100644 --- a/microsite/data/plugins/digital.ai-deploy.yaml +++ b/microsite/data/plugins/digital.ai-deploy.yaml @@ -6,7 +6,7 @@ category: CI/CD description: The plugin offers integration with Digital.ai Deploy and backstage components and services. It provide access to deployments and reports. documentation: https://docs.digital.ai/deploy/docs/concept/xl-deploy-backstage-overview iconUrl: /img/digital.ai-deploy.svg -npmPackageName: '@digital.ai/plugin-dai-deploy' +npmPackageName: '@digital-ai/plugin-dai-deploy' tags: - ci - cd diff --git a/microsite/data/plugins/firehydrant.yaml b/microsite/data/plugins/firehydrant.yaml index 7aa00675b4..f5ccd3629d 100644 --- a/microsite/data/plugins/firehydrant.yaml +++ b/microsite/data/plugins/firehydrant.yaml @@ -6,5 +6,5 @@ category: Monitoring description: View service incidents information from FireHydrant, such as active incidents and incident metrics, directly within Backstage. documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/firehydrant/plugins/firehydrant/README.md iconUrl: https://github.com/backstage/community-plugins/blob/main/workspaces/firehydrant/plugins/firehydrant/doc/firehydrant_logo.png -npmPackageName: '@backstage/plugin-firehydrant' +npmPackageName: '@backstage-community/plugin-firehydrant' addedDate: '2021-08-18' diff --git a/microsite/data/plugins/fossa.yaml b/microsite/data/plugins/fossa.yaml index a1a66eb7d6..34610c64e4 100644 --- a/microsite/data/plugins/fossa.yaml +++ b/microsite/data/plugins/fossa.yaml @@ -6,5 +6,5 @@ category: Quality description: View FOSSA license compliance of your components in Backstage. documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/fossa/plugins/fossa/README.md iconUrl: https://avatars0.githubusercontent.com/u/9543448?s=400&v=4 -npmPackageName: '@backstage/plugin-fossa' +npmPackageName: '@backstage-community/plugin-fossa' addedDate: '2020-12-10' diff --git a/microsite/data/plugins/kubernetes-provider.yaml b/microsite/data/plugins/kubernetes-provider.yaml index e0971d55be..b23ab03caa 100644 --- a/microsite/data/plugins/kubernetes-provider.yaml +++ b/microsite/data/plugins/kubernetes-provider.yaml @@ -6,5 +6,5 @@ category: Kubernetes description: Import Kubernetes resources into Backstage Components documentation: https://github.com/AntoineDao/backstage-provider-kubernetes#readme iconUrl: https://avatars.githubusercontent.com/u/13629408 -npmPackageName: https://www.npmjs.com/package/@antoinedao/backstage-provider-kubernetes +npmPackageName: '@antoinedao/backstage-provider-kubernetes' addedDate: '2023-04-10' diff --git a/microsite/data/plugins/linguist.yaml b/microsite/data/plugins/linguist.yaml index fb8e88b747..15b79b3682 100644 --- a/microsite/data/plugins/linguist.yaml +++ b/microsite/data/plugins/linguist.yaml @@ -6,5 +6,5 @@ category: Metadata description: View the programming language break down for your entities within the Software Catalog documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/linguist/plugins/linguist/README.md iconUrl: /img/linguist.svg -npmPackageName: '@backstage/plugin-linguist' +npmPackageName: '@backstage-community/plugin-linguist' addedDate: '2023-06-17' diff --git a/microsite/data/plugins/octopus-deploy.yaml b/microsite/data/plugins/octopus-deploy.yaml index cfc5e2c9e5..eb19f60e72 100644 --- a/microsite/data/plugins/octopus-deploy.yaml +++ b/microsite/data/plugins/octopus-deploy.yaml @@ -6,5 +6,5 @@ category: CI/CD description: Easily view your Octopus Deploy releases within the Software Catalog documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/octopus-deploy/plugins/octopus-deploy/README.md iconUrl: /img/octopus-deploy.svg -npmPackageName: '@backstage/plugin-octopus-deploy' +npmPackageName: '@backstage-community/plugin-octopus-deploy' addedDate: '2023-02-24' diff --git a/microsite/data/plugins/opencost.yaml b/microsite/data/plugins/opencost.yaml index 95973e3b0c..9e98c85bcb 100644 --- a/microsite/data/plugins/opencost.yaml +++ b/microsite/data/plugins/opencost.yaml @@ -6,5 +6,5 @@ category: Monitoring description: OpenCost provides cloud cost monitoring for your cloud native environments. documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/opencost/plugins/opencost/README.md iconUrl: /img/opencost.png -npmPackageName: '@backstage/plugin-opencost' +npmPackageName: '@backstage-community/plugin-opencost' addedDate: '2023-10-26' diff --git a/microsite/data/plugins/puppetdb.yaml b/microsite/data/plugins/puppetdb.yaml index 650d8318d3..26e8bbd3ef 100644 --- a/microsite/data/plugins/puppetdb.yaml +++ b/microsite/data/plugins/puppetdb.yaml @@ -6,7 +6,7 @@ category: Configuration Management description: Visualize resource information and Puppet facts from PuppetDB. documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/puppetdb/plugins/puppetdb/README.md iconUrl: /img/puppet.png -npmPackageName: '@backstage/plugin-puppetdb' +npmPackageName: '@backstage-community/plugin-puppetdb' tags: - puppet - puppetdb diff --git a/microsite/data/plugins/renovate-hoster.yaml b/microsite/data/plugins/renovate-hoster.yaml index 4cf4f65956..06187581bf 100644 --- a/microsite/data/plugins/renovate-hoster.yaml +++ b/microsite/data/plugins/renovate-hoster.yaml @@ -5,5 +5,5 @@ authorUrl: https://github.com/secustor category: Quality description: This plugin enables you to host Renovate CLI yourself inside of Backstage and extract data from it. documentation: https://github.com/secustor/backstage-plugins/tree/main/plugins/renovate -npmPackageName: '@backstage/backstage-plugin-renovate' +npmPackageName: '@secustor/backstage-plugin-renovate' addedDate: '2025-03-31' diff --git a/microsite/data/plugins/sds_workspace.yaml b/microsite/data/plugins/sds_workspace.yaml new file mode 100644 index 0000000000..43d9a0f20e --- /dev/null +++ b/microsite/data/plugins/sds_workspace.yaml @@ -0,0 +1,10 @@ +--- +title: Citrix Secure Developer Spaces (SDS) +author: Citrix +authorUrl: https://github.com/strong-network +category: Development +description: Connect Backstage with Citrix Secure Developer Spaces (SDS) to view and create developer workspaces. +documentation: https://www.npmjs.com/package/@citrixcloud/backstage-sds-workspaces +iconUrl: /img/citrix_sds.png +npmPackageName: '@citrixcloud/backstage-sds-workspaces' +addedDate: '2025-10-07' diff --git a/microsite/data/plugins/shortcuts.yaml b/microsite/data/plugins/shortcuts.yaml index b420e28e2f..7d4e050df0 100644 --- a/microsite/data/plugins/shortcuts.yaml +++ b/microsite/data/plugins/shortcuts.yaml @@ -6,5 +6,5 @@ category: Utility description: The shortcuts plugin allows a user to have easy access to pages within a Backstage app by storing them as "shortcuts" in the Sidebar. documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/shortcuts/plugins/shortcuts/README.md iconUrl: /img/shortcuts.svg -npmPackageName: '@backstage/plugin-shortcuts' +npmPackageName: '@backstage-community/plugin-shortcuts' addedDate: '2021-10-06' diff --git a/microsite/data/plugins/sonarqube.yaml b/microsite/data/plugins/sonarqube.yaml index 72c662e2fa..48793adea9 100644 --- a/microsite/data/plugins/sonarqube.yaml +++ b/microsite/data/plugins/sonarqube.yaml @@ -6,5 +6,5 @@ category: Quality description: Components to display code quality metrics from SonarCloud and SonarQube. documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/sonarqube/plugins/sonarqube/README.md iconUrl: /img/sonarqube-icon.svg -npmPackageName: '@backstage/plugin-sonarqube' +npmPackageName: '@backstage-community/plugin-sonarqube' addedDate: '2020-11-03' diff --git a/microsite/data/plugins/stack-overflow.yaml b/microsite/data/plugins/stack-overflow.yaml index b058ce90fe..373c08de8f 100644 --- a/microsite/data/plugins/stack-overflow.yaml +++ b/microsite/data/plugins/stack-overflow.yaml @@ -6,5 +6,5 @@ category: Discovery description: Provides Stack Overflow specific functionality that can be used in different ways (e.g. for homepage and search) to compose your Backstage App. documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/stack-overflow/plugins/stack-overflow/README.md iconUrl: /img/stack-overflow-logo.svg -npmPackageName: '@backstage/plugin-stack-overflow' +npmPackageName: '@backstage-community/plugin-stack-overflow' addedDate: '2022-06-14' diff --git a/microsite/data/plugins/template-designer.yaml b/microsite/data/plugins/template-designer.yaml new file mode 100644 index 0000000000..86aa8a97f0 --- /dev/null +++ b/microsite/data/plugins/template-designer.yaml @@ -0,0 +1,10 @@ +--- +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 +npmPackageName: '@tduniec/plugin-template-designer' +addedDate: 2025-11-19 diff --git a/microsite/data/plugins/torque.yaml b/microsite/data/plugins/torque.yaml index 1480c3bad9..a469c455fc 100644 --- a/microsite/data/plugins/torque.yaml +++ b/microsite/data/plugins/torque.yaml @@ -8,5 +8,5 @@ description: | Plugin includes an Entity ComponentCard, Backend API route and scaffolder actions. documentation: https://github.com/QualiTorque/torque-backstage-plugin/tree/main/packages/torque#readme iconUrl: https://user-images.githubusercontent.com/8643801/214640977-751bc338-6b77-40a0-a897-cba41b6f006a.png -npmPackageName: '@qtorque/backstage-plugin-torque' +npmPackageName: '@qtorque/backstage-torque-plugin' addedDate: '2023-01-25' diff --git a/microsite/data/plugins/xcmetrics.yaml b/microsite/data/plugins/xcmetrics.yaml index f02b195eb5..09a8b1547f 100644 --- a/microsite/data/plugins/xcmetrics.yaml +++ b/microsite/data/plugins/xcmetrics.yaml @@ -6,5 +6,5 @@ category: Monitoring description: Discover valuable insights hiding inside Xcode’s build logs. documentation: https://xcmetrics.io/ iconUrl: /img/xcmetrics-icon.png -npmPackageName: '@backstage/plugin-xcmetrics' +npmPackageName: '@backstage-community/plugin-xcmetrics' addedDate: '2021-08-06' diff --git a/microsite/package.json b/microsite/package.json index 81985a0e91..edf07abb02 100644 --- a/microsite/package.json +++ b/microsite/package.json @@ -28,7 +28,7 @@ "@docusaurus/preset-classic": "^3.1.1", "@docusaurus/types": "^3.1.1", "@mdx-js/react": "^3.0.0", - "@swc/core": "^1.3.46", + "@swc/core": "^1.15.6", "clsx": "^2.0.0", "docusaurus-plugin-openapi-docs": "^4.3.0", "docusaurus-plugin-sass": "^0.2.3", @@ -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.1", "prettier": "^2.6.2", - "typescript": "~5.2.0", + "typescript": "~5.7.0", "yaml-loader": "^0.8.0" } } diff --git a/microsite/sidebars.ts b/microsite/sidebars.ts index 932a378275..afe5616bbf 100644 --- a/microsite/sidebars.ts +++ b/microsite/sidebars.ts @@ -18,677 +18,743 @@ const scaffolderSidebar = tryToLoadCustomSidebar( '../docs/features/software-templates/api/sidebar.ts', ); +function sidebarElementWithIndex( + element: { + description?: string; + differentiator?: string; + label: string; + docId?: string; + }, + children: Array, +) { + const { label, description, differentiator } = element; + return { + type: 'category', + label: label, + description, + link: { + type: 'generated-index', + title: label, + slug: `/${differentiator}${label + .toLowerCase() + .replace(/[^a-z0-9 _-]/gi, '-')}/generated-index`, + }, + items: children, + }; +} + export default { - docs: { - Overview: [ + docs: [ + sidebarElementWithIndex({ label: 'Overview' }, [ 'overview/what-is-backstage', 'overview/technical-overview', 'overview/architecture-overview', 'overview/roadmap', 'overview/threat-model', 'overview/versioning-policy', - ], - 'Getting Started': [ + ]), + sidebarElementWithIndex({ label: 'Getting Started' }, [ 'getting-started/index', - { - type: 'category', - label: 'Configuring Backstage', - items: [ - 'getting-started/config/database', - 'getting-started/config/authentication', - 'getting-started/configure-app-with-plugins', - 'getting-started/homepage', - ], - }, - { - type: 'category', - label: 'Deploying Backstage', - items: [ - 'deployment/index', - 'deployment/scaling', - 'deployment/docker', - 'deployment/k8s', - ], - }, - { - type: 'category', - label: 'Using Backstage', - items: [ - 'getting-started/logging-in', - 'getting-started/viewing-catalog', - 'getting-started/view-what-you-own', - 'getting-started/viewing-entity-relationships', - 'getting-started/search-catalog', - { - type: 'category', - label: 'Managing Components', - items: [ - 'getting-started/register-a-component', - 'getting-started/create-a-component', - 'getting-started/update-a-component', - 'getting-started/unregister-delete-component', - ], - }, - - ], - }, + sidebarElementWithIndex({ label: 'Configuring Backstage' }, [ + 'getting-started/config/database', + 'getting-started/config/authentication', + 'getting-started/configure-app-with-plugins', + 'getting-started/homepage', + ]), + sidebarElementWithIndex({ label: 'Deploying Backstage' }, [ + 'deployment/index', + 'deployment/scaling', + 'deployment/docker', + 'deployment/k8s', + ]), + sidebarElementWithIndex({ label: 'Using Backstage' }, [ + 'getting-started/logging-in', + 'getting-started/register-a-component', + 'getting-started/create-a-component', + ]), 'overview/support', 'getting-started/keeping-backstage-updated', - ], + ]), ...(process.env.GOLDEN_PATH - ? { - 'Golden Paths': [ + ? [ + sidebarElementWithIndex({ label: 'Golden Paths' }, [ + sidebarElementWithIndex({ label: '001 - Create an App' }, [ + 'golden-path/create-app/index', + 'golden-path/create-app/npx-create-app', + 'golden-path/create-app/local-development', + 'golden-path/create-app/installing-plugins', + 'golden-path/create-app/logging-in', + 'golden-path/create-app/custom-theme', + 'golden-path/create-app/keeping-backstage-updated', + ]), + sidebarElementWithIndex({ label: '002 - Plugins' }, [ + 'golden-path/plugins/index', + 'golden-path/plugins/why-build-plugins', + 'golden-path/plugins/sustainable-plugin-development', + sidebarElementWithIndex({ label: 'Backend Plugins' }, [ + 'golden-path/plugins/backend/001-first-steps', + 'golden-path/plugins/backend/002-poking-around', + ]), + ]), + ]), + ] + : []), + sidebarElementWithIndex( + { + label: 'Core Features', + description: 'Features powering the core of Backstage.', + }, + [ + sidebarElementWithIndex( + { + label: 'Auth and Identity', + description: 'Authentication and identity management features.', + }, + [ + 'auth/index', + sidebarElementWithIndex( + { + label: 'Included providers', + description: + 'Pre-configured authentication providers included with Backstage.', + }, + [ + 'auth/auth0/provider', + 'auth/atlassian/provider', + 'auth/aws-alb/provider', + 'auth/microsoft/provider', + 'auth/microsoft/easy-auth', + 'auth/bitbucket/provider', + 'auth/bitbucketServer/provider', + 'auth/cloudflare/provider', + 'auth/github/provider', + 'auth/gitlab/provider', + 'auth/google/provider', + 'auth/google/gcp-iap-auth', + 'auth/guest/provider', + 'auth/okta/provider', + 'auth/oauth2-proxy/provider', + 'auth/onelogin/provider', + 'auth/vmware-cloud/provider', + ], + ), + 'auth/identity-resolver', + 'auth/oauth', + 'auth/oidc', + 'auth/add-auth-provider', + 'auth/service-to-service-auth', + 'auth/autologout', + 'auth/troubleshooting', + ], + ), + sidebarElementWithIndex( + { + label: 'Kubernetes', + description: 'Use Kubernetes right from Backstage.', + }, + [ + 'features/kubernetes/overview', + 'features/kubernetes/installation', + 'features/kubernetes/configuration', + 'features/kubernetes/authentication', + 'features/kubernetes/authentication-strategies', + 'features/kubernetes/troubleshooting', + 'features/kubernetes/proxy', + ], + ), + sidebarElementWithIndex( + { + label: 'Notifications', + description: 'Know when important events happen in Backstage.', + }, + [ + 'notifications/index', + 'notifications/processors', + 'notifications/usage', + ], + ), + sidebarElementWithIndex( + { + label: 'Permissions', + description: 'Access control for your Backstage instance.', + }, + [ + 'permissions/overview', + 'permissions/concepts', + 'permissions/getting-started', + 'permissions/writing-a-policy', + 'permissions/frontend-integration', + 'permissions/custom-rules', + sidebarElementWithIndex( + { + label: 'Tutorial: using Permissions in your plugin', + description: + 'Step-by-step guide for plugin authors on using Permissions.', + }, + [ + 'permissions/plugin-authors/01-setup', + 'permissions/plugin-authors/02-adding-a-basic-permission-check', + 'permissions/plugin-authors/03-adding-a-resource-permission-check', + 'permissions/plugin-authors/04-authorizing-access-to-paginated-data', + 'permissions/plugin-authors/05-frontend-authorization', + ], + ), + ], + ), + sidebarElementWithIndex( + { label: 'Search', description: 'Using Search within Backstage.' }, + [ + 'features/search/search-overview', + 'features/search/getting-started', + 'features/search/concepts', { type: 'category', - label: '001 - create-app', - items: [ - 'golden-path/create-app/index', - 'golden-path/create-app/npx-create-app', - 'golden-path/create-app/local-development', - 'golden-path/create-app/installing-plugins', - 'golden-path/create-app/logging-in', - 'golden-path/create-app/custom-theme', - 'golden-path/create-app/keeping-backstage-updated', - ], + label: 'API', + link: + searchSidebar.length > 0 + ? { + type: 'generated-index', + title: 'Search API', + slug: '/category/search-api', + } + : { + type: 'doc', + id: 'openapi/generated-docs/404', + }, + items: searchSidebar, }, + 'features/search/architecture', + 'features/search/search-engines', + 'features/search/collators', + 'features/search/how-to-guides', + ], + ), + sidebarElementWithIndex( + { + label: 'Software Catalog', + description: 'Manage and explore your software components.', + }, + [ + 'features/software-catalog/software-catalog-overview', + 'features/software-catalog/life-of-an-entity', + 'features/software-catalog/configuration', + 'features/software-catalog/system-model', + 'features/software-catalog/descriptor-format', + 'features/software-catalog/references', + 'features/software-catalog/well-known-annotations', + 'features/software-catalog/well-known-relations', + 'features/software-catalog/well-known-statuses', + 'features/software-catalog/extending-the-model', + 'features/software-catalog/external-integrations', + 'features/software-catalog/catalog-customization', { type: 'category', - label: '002 - Plugins', - items: [ - 'golden-path/plugins/index', - 'golden-path/plugins/why-build-plugins', - 'golden-path/plugins/sustainable-plugin-development', - { - type: 'category', - label: 'Backend Plugins', - items: [ - 'golden-path/plugins/backend/001-first-steps', - 'golden-path/plugins/backend/002-poking-around', - ], - }, - ], + label: 'API', + link: + catalogSidebar.length > 0 + ? { + type: 'generated-index', + title: 'Catalog API', + slug: '/category/catalog-api', + } + : { + type: 'doc', + id: 'openapi/generated-docs/404', + }, + items: catalogSidebar, + }, + 'features/software-catalog/creating-the-catalog-graph', + 'features/software-catalog/faq', + ], + ), + sidebarElementWithIndex( + { + label: 'Software Templates', + description: 'Create and manage software templates.', + }, + [ + 'features/software-templates/software-templates-index', + 'features/software-templates/configuration', + 'features/software-templates/adding-templates', + 'features/software-templates/writing-templates', + 'features/software-templates/input-examples', + 'features/software-templates/builtin-actions', + 'features/software-templates/writing-custom-actions', + 'features/software-templates/writing-tests-for-actions', + 'features/software-templates/writing-custom-field-extensions', + 'features/software-templates/writing-custom-step-layouts', + 'features/software-templates/authorizing-scaffolder-template-details', + 'features/software-templates/migrating-to-rjsf-v5', + 'features/software-templates/migrating-from-v1beta2-to-v1beta3', + 'features/software-templates/dry-run-testing', + 'features/software-templates/experimental', + 'features/software-templates/templating-extensions', + { + type: 'category', + label: 'API', + link: + scaffolderSidebar.length > 0 + ? { + type: 'generated-index', + title: 'Scaffolder API', + slug: '/category/scaffolder-api', + } + : { + type: 'doc', + id: 'openapi/generated-docs/404', + }, + items: scaffolderSidebar, }, ], - } - : {}), - 'Core Features': [ - { - type: 'category', - label: 'Auth and Identity', - items: [ - 'auth/index', + ), + sidebarElementWithIndex( { - type: 'category', - label: 'Included providers', - items: [ - 'auth/auth0/provider', - 'auth/atlassian/provider', - 'auth/aws-alb/provider', - 'auth/microsoft/provider', - 'auth/microsoft/easy-auth', - 'auth/bitbucket/provider', - 'auth/bitbucketServer/provider', - 'auth/cloudflare/provider', - 'auth/github/provider', - 'auth/gitlab/provider', - 'auth/google/provider', - 'auth/google/gcp-iap-auth', - 'auth/guest/provider', - 'auth/okta/provider', - 'auth/oauth2-proxy/provider', - 'auth/onelogin/provider', - 'auth/vmware-cloud/provider', - ], + label: 'TechDocs', + description: 'Documentation as code for your software.', }, - 'auth/identity-resolver', - 'auth/oauth', - 'auth/oidc', - 'auth/add-auth-provider', - 'auth/service-to-service-auth', - 'auth/autologout', - 'auth/troubleshooting', - ], - }, + [ + 'features/techdocs/techdocs-overview', + 'features/techdocs/getting-started', + 'features/techdocs/concepts', + 'features/techdocs/addons', + 'features/techdocs/architecture', + 'features/techdocs/extensions', + 'features/techdocs/creating-and-publishing', + 'features/techdocs/configuration', + 'features/techdocs/using-cloud-storage', + 'features/techdocs/configuring-ci-cd', + 'features/techdocs/cli', + 'features/techdocs/how-to-guides', + 'features/techdocs/troubleshooting', + 'features/techdocs/faqs', + ], + ), + ], + ), + sidebarElementWithIndex( { - type: 'category', - label: 'Kubernetes', - items: [ - 'features/kubernetes/overview', - 'features/kubernetes/installation', - 'features/kubernetes/configuration', - 'features/kubernetes/authentication', - 'features/kubernetes/authentication-strategies', - 'features/kubernetes/troubleshooting', - 'features/kubernetes/proxy', - ], + label: 'Integrations', + description: 'Connect and integrate with external services.', }, - { - type: 'category', - label: 'Notifications', - items: [ - 'notifications/index', - 'notifications/processors', - 'notifications/usage', - ], - }, - { - type: 'category', - label: 'Permissions', - items: [ - 'permissions/overview', - 'permissions/concepts', - 'permissions/getting-started', - 'permissions/writing-a-policy', - 'permissions/frontend-integration', - 'permissions/custom-rules', - { - type: 'category', - label: 'Tutorial: using Permissions in your plugin', - items: [ - 'permissions/plugin-authors/01-setup', - 'permissions/plugin-authors/02-adding-a-basic-permission-check', - 'permissions/plugin-authors/03-adding-a-resource-permission-check', - 'permissions/plugin-authors/04-authorizing-access-to-paginated-data', - 'permissions/plugin-authors/05-frontend-authorization', - ], - }, - ], - }, - { - type: 'category', - label: 'Search', - items: [ - 'features/search/search-overview', - 'features/search/getting-started', - 'features/search/concepts', - { - type: 'category', - label: 'API', - link: - searchSidebar.length > 0 - ? { - type: 'generated-index', - title: 'Search API', - slug: '/category/search-api', - } - : { - type: 'doc', - id: 'openapi/generated-docs/404', - }, - items: searchSidebar, - }, - 'features/search/architecture', - 'features/search/search-engines', - 'features/search/collators', - 'features/search/how-to-guides', - ], - }, - { - type: 'category', - label: 'Software Catalog', - items: [ - 'features/software-catalog/software-catalog-overview', - 'features/software-catalog/life-of-an-entity', - 'features/software-catalog/configuration', - 'features/software-catalog/system-model', - 'features/software-catalog/descriptor-format', - 'features/software-catalog/references', - 'features/software-catalog/well-known-annotations', - 'features/software-catalog/well-known-relations', - 'features/software-catalog/well-known-statuses', - 'features/software-catalog/extending-the-model', - 'features/software-catalog/external-integrations', - 'features/software-catalog/catalog-customization', - { - type: 'category', - label: 'API', - link: - catalogSidebar.length > 0 - ? { - type: 'generated-index', - title: 'Catalog API', - slug: '/category/catalog-api', - } - : { - type: 'doc', - id: 'openapi/generated-docs/404', - }, - items: catalogSidebar, - }, - 'features/software-catalog/creating-the-catalog-graph', - 'features/software-catalog/faq', - ], - }, - { - type: 'category', - label: 'Software Templates', - items: [ - 'features/software-templates/software-templates-index', - 'features/software-templates/configuration', - 'features/software-templates/adding-templates', - 'features/software-templates/writing-templates', - 'features/software-templates/input-examples', - 'features/software-templates/builtin-actions', - 'features/software-templates/writing-custom-actions', - 'features/software-templates/writing-tests-for-actions', - 'features/software-templates/writing-custom-field-extensions', - 'features/software-templates/writing-custom-step-layouts', - 'features/software-templates/authorizing-scaffolder-template-details', - 'features/software-templates/migrating-to-rjsf-v5', - 'features/software-templates/migrating-from-v1beta2-to-v1beta3', - 'features/software-templates/dry-run-testing', - 'features/software-templates/experimental', - 'features/software-templates/templating-extensions', - { - type: 'category', - label: 'API', - link: - scaffolderSidebar.length > 0 - ? { - type: 'generated-index', - title: 'Scaffolder API', - slug: '/category/scaffolder-api', - } - : { - type: 'doc', - id: 'openapi/generated-docs/404', - }, - items: scaffolderSidebar, - }, - ], - }, - { - type: 'category', - label: 'TechDocs', - items: [ - 'features/techdocs/techdocs-overview', - 'features/techdocs/getting-started', - 'features/techdocs/concepts', - 'features/techdocs/addons', - 'features/techdocs/architecture', - 'features/techdocs/extensions', - 'features/techdocs/creating-and-publishing', - 'features/techdocs/configuration', - 'features/techdocs/using-cloud-storage', - 'features/techdocs/configuring-ci-cd', - 'features/techdocs/cli', - 'features/techdocs/how-to-guides', - 'features/techdocs/troubleshooting', - 'features/techdocs/faqs', - ], - }, - ], - Integrations: [ - 'integrations/index', - { - type: 'category', - label: 'AWS S3', - items: [ + [ + 'integrations/index', + sidebarElementWithIndex({ label: 'AWS S3' }, [ 'integrations/aws-s3/locations', 'integrations/aws-s3/discovery', - ], - }, - { - type: 'category', - label: 'Azure Blob Storage', - items: [ + ]), + sidebarElementWithIndex({ label: 'Azure Blob Storage' }, [ 'integrations/azure-blobStorage/locations', 'integrations/azure-blobStorage/discovery', - ], - }, - { - type: 'category', - label: 'Azure', - items: [ + ]), + sidebarElementWithIndex({ label: 'Azure' }, [ 'integrations/azure/locations', 'integrations/azure/discovery', 'integrations/azure/org', - ], - }, - { - type: 'category', - label: 'Bitbucket Cloud', - items: [ + ]), + sidebarElementWithIndex({ label: 'Bitbucket Cloud' }, [ 'integrations/bitbucketCloud/locations', 'integrations/bitbucketCloud/discovery', - ], - }, - { - type: 'category', - label: 'Bitbucket Server', - items: [ + ]), + sidebarElementWithIndex({ label: 'Bitbucket Server' }, [ 'integrations/bitbucketServer/locations', 'integrations/bitbucketServer/discovery', - ], - }, - { - type: 'category', - label: 'Datadog', - items: ['integrations/datadog-rum/installation'], - }, - { - type: 'category', - label: 'Gerrit', - items: [ + ]), + sidebarElementWithIndex({ label: 'Datadog' }, [ + 'integrations/datadog-rum/installation', + ]), + sidebarElementWithIndex({ label: 'Gerrit' }, [ 'integrations/gerrit/locations', 'integrations/gerrit/discovery', - ], - }, - { - type: 'category', - label: 'GitHub', - items: [ + ]), + sidebarElementWithIndex({ label: 'Github' }, [ 'integrations/github/locations', 'integrations/github/discovery', 'integrations/github/org', 'integrations/github/github-apps', - ], - }, - { - type: 'category', - label: 'GitLab', - items: [ + ]), + sidebarElementWithIndex({ label: 'GitLab' }, [ 'integrations/gitlab/locations', 'integrations/gitlab/discovery', 'integrations/gitlab/org', - ], - }, + ]), + sidebarElementWithIndex({ label: 'Gitea' }, [ + 'integrations/gitea/locations', + 'integrations/gitea/discovery', + ]), + sidebarElementWithIndex({ label: 'Harness' }, [ + 'integrations/harness/locations', + ]), + sidebarElementWithIndex({ label: 'Google GCS' }, [ + 'integrations/google-cloud-storage/locations', + ]), + sidebarElementWithIndex({ label: 'LDAP' }, ['integrations/ldap/org']), + ], + ), + sidebarElementWithIndex( { - type: 'category', - label: 'Gitea', - items: ['integrations/gitea/locations', 'integrations/gitea/discovery'], + label: 'Plugins', + description: 'Extend Backstage with custom functionality.', }, - { - type: 'category', - label: 'Harness', - items: ['integrations/harness/locations'], - }, - { - type: 'category', - label: 'Google GCS', - items: ['integrations/google-cloud-storage/locations'], - }, - { - type: 'category', - label: 'LDAP', - items: ['integrations/ldap/org'], - }, - ], - Plugins: [ - 'plugins/index', - 'plugins/existing-plugins', - 'plugins/create-a-plugin', - 'plugins/plugin-development', - 'plugins/structure-of-a-plugin', - 'plugins/integrating-plugin-into-software-catalog', - 'plugins/integrating-search-into-plugins', - 'plugins/composability', - 'plugins/internationalization', - 'plugins/analytics', - 'plugins/feature-flags', - { - type: 'category', - label: 'OpenAPI', - items: [ - 'openapi/01-getting-started', - 'openapi/generate-client', - 'openapi/test-case-validation', - ], - }, - { - type: 'category', - label: 'Backends and APIs', - items: [ - 'plugins/proxying', - 'plugins/backend-plugin', - 'plugins/call-existing-api', - ], - }, - { - type: 'category', - label: 'Testing', - items: ['plugins/testing'], - }, - { - type: 'category', - label: 'Publishing', - items: [ - 'plugins/publish-private', - 'plugins/add-to-directory', - 'plugins/observability', - ], - }, - ], - Configuration: [ - 'conf/index', - 'conf/reading', - 'conf/writing', - 'conf/defining', - ], - Framework: [ - { - type: 'category', - label: 'Backend System', - items: [ - 'backend-system/index', + [ + 'plugins/index', + 'plugins/existing-plugins', + 'plugins/create-a-plugin', + 'plugins/plugin-development', + 'plugins/structure-of-a-plugin', + 'plugins/integrating-plugin-into-software-catalog', + 'plugins/integrating-search-into-plugins', + 'plugins/composability', + 'plugins/internationalization', + 'plugins/analytics', + 'plugins/feature-flags', + sidebarElementWithIndex( { - type: 'category', - label: 'Architecture', - items: [ - 'backend-system/architecture/index', - 'backend-system/architecture/services', - 'backend-system/architecture/plugins', - 'backend-system/architecture/extension-points', - 'backend-system/architecture/modules', - 'backend-system/architecture/feature-loaders', - 'backend-system/architecture/naming-patterns', - ], + label: 'OpenAPI', + description: + 'Work with OpenAPI specifications and generate clients.', }, + [ + 'openapi/01-getting-started', + 'openapi/generate-client', + 'openapi/test-case-validation', + ], + ), + sidebarElementWithIndex( { - type: 'category', - label: 'Building Backends', - items: [ - 'backend-system/building-backends/index', - 'backend-system/building-backends/migrating', - ], + label: 'Backends and APIs', + description: 'Build and manage backend services and APIs.', }, - { - type: 'category', - label: 'Building Plugins & Modules', - items: [ - 'backend-system/building-plugins-and-modules/index', - 'backend-system/building-plugins-and-modules/testing', - 'backend-system/building-plugins-and-modules/migrating', - ], - }, - { - type: 'category', - label: 'Core Services', - items: [ - 'backend-system/core-services/index', - 'backend-system/core-services/auditor', - 'backend-system/core-services/auth', - 'backend-system/core-services/cache', - 'backend-system/core-services/database', - 'backend-system/core-services/discovery', - 'backend-system/core-services/http-auth', - 'backend-system/core-services/http-router', - 'backend-system/core-services/identity', - 'backend-system/core-services/lifecycle', - 'backend-system/core-services/logger', - 'backend-system/core-services/permissions', - 'backend-system/core-services/permissions-registry', - 'backend-system/core-services/plugin-metadata', - 'backend-system/core-services/root-config', - 'backend-system/core-services/root-health', - 'backend-system/core-services/root-http-router', - 'backend-system/core-services/root-lifecycle', - 'backend-system/core-services/root-logger', - 'backend-system/core-services/scheduler', - 'backend-system/core-services/token-manager', - 'backend-system/core-services/url-reader', - 'backend-system/core-services/user-info', - 'backend-system/core-services/actions-registry', - 'backend-system/core-services/actions', - ], - }, - ], - }, + [ + 'plugins/proxying', + 'plugins/backend-plugin', + 'plugins/call-existing-api', + ], + ), + sidebarElementWithIndex( + { label: 'Testing', description: 'Testing plugins and modules.' }, + ['plugins/testing'], + ), + sidebarElementWithIndex( + { label: 'Publishing', description: 'Publishing your plugins.' }, + ['plugins/publish-private', 'plugins/add-to-directory'], + ), + 'plugins/observability', + ], + ), + sidebarElementWithIndex( { - type: 'category', - label: 'New Frontend System', - items: [ - 'frontend-system/index', + label: 'Configuration', + description: 'Manage static configuration files and settings.', + }, + ['conf/index', 'conf/reading', 'conf/writing', 'conf/defining'], + ), + sidebarElementWithIndex( + { + label: 'Framework', + description: 'Core framework concepts and architecture.', + }, + [ + sidebarElementWithIndex( { - type: 'category', - label: 'Architecture', - items: [ - 'frontend-system/architecture/index', - 'frontend-system/architecture/app', - 'frontend-system/architecture/plugins', - 'frontend-system/architecture/extensions', - 'frontend-system/architecture/extension-blueprints', - 'frontend-system/architecture/extension-overrides', - 'frontend-system/architecture/references', - 'frontend-system/architecture/utility-apis', - 'frontend-system/architecture/routes', - 'frontend-system/architecture/naming-patterns', - 'frontend-system/architecture/migrations', - ], + label: 'Backend System', + description: 'Backend system components and architecture.', }, + [ + 'backend-system/index', + sidebarElementWithIndex( + { + label: 'Architecture', + description: 'Architecture of the backend system.', + }, + [ + 'backend-system/architecture/index', + 'backend-system/architecture/services', + 'backend-system/architecture/plugins', + 'backend-system/architecture/extension-points', + 'backend-system/architecture/modules', + 'backend-system/architecture/feature-loaders', + 'backend-system/architecture/naming-patterns', + ], + ), + sidebarElementWithIndex( + { + label: 'Building Backends', + description: 'Guides on building backend systems.', + }, + [ + 'backend-system/building-backends/index', + 'backend-system/building-backends/migrating', + ], + ), + sidebarElementWithIndex( + { + label: 'Building Plugins & Modules', + description: 'Guides on building plugins and modules.', + }, + [ + 'backend-system/building-plugins-and-modules/index', + 'backend-system/building-plugins-and-modules/testing', + 'backend-system/building-plugins-and-modules/migrating', + ], + ), + sidebarElementWithIndex( + { + label: 'Core Services', + description: 'Core services of the backend system.', + }, + [ + 'backend-system/core-services/index', + 'backend-system/core-services/auditor', + 'backend-system/core-services/auth', + 'backend-system/core-services/cache', + 'backend-system/core-services/database', + 'backend-system/core-services/discovery', + 'backend-system/core-services/http-auth', + 'backend-system/core-services/http-router', + 'backend-system/core-services/identity', + 'backend-system/core-services/lifecycle', + 'backend-system/core-services/logger', + 'backend-system/core-services/permissions', + 'backend-system/core-services/permissions-registry', + 'backend-system/core-services/plugin-metadata', + 'backend-system/core-services/root-config', + 'backend-system/core-services/root-health', + 'backend-system/core-services/root-http-router', + 'backend-system/core-services/root-instance-metadata', + 'backend-system/core-services/root-lifecycle', + 'backend-system/core-services/root-logger', + 'backend-system/core-services/scheduler', + 'backend-system/core-services/token-manager', + 'backend-system/core-services/url-reader', + 'backend-system/core-services/user-info', + 'backend-system/core-services/actions-registry', + 'backend-system/core-services/actions', + ], + ), + ], + ), + sidebarElementWithIndex( { - type: 'category', - label: 'Building Plugins', - items: [ - 'frontend-system/building-plugins/index', - 'frontend-system/building-plugins/testing', - 'frontend-system/building-plugins/common-extension-blueprints', - 'frontend-system/building-plugins/built-in-data-refs', - 'frontend-system/building-plugins/migrating', - ], + label: 'New Frontend System', + description: 'New frontend system components and architecture.', }, + [ + 'frontend-system/index', + sidebarElementWithIndex( + { + label: 'Architecture', + description: 'Architecture of the new frontend system.', + }, + [ + 'frontend-system/architecture/index', + 'frontend-system/architecture/app', + 'frontend-system/architecture/plugins', + 'frontend-system/architecture/extensions', + 'frontend-system/architecture/extension-blueprints', + 'frontend-system/architecture/extension-overrides', + 'frontend-system/architecture/references', + 'frontend-system/architecture/utility-apis', + 'frontend-system/architecture/routes', + 'frontend-system/architecture/naming-patterns', + 'frontend-system/architecture/migrations', + ], + ), + sidebarElementWithIndex( + { + label: 'Building Plugins', + description: 'Guides on building frontend plugins.', + }, + [ + 'frontend-system/building-plugins/index', + 'frontend-system/building-plugins/testing', + 'frontend-system/building-plugins/common-extension-blueprints', + 'frontend-system/building-plugins/built-in-data-refs', + 'frontend-system/building-plugins/migrating', + ], + ), + sidebarElementWithIndex( + { + label: 'Building Apps', + description: 'Guides on building frontend applications.', + }, + [ + 'frontend-system/building-apps/index', + 'frontend-system/building-apps/configuring-extensions', + 'frontend-system/building-apps/built-in-extensions', + 'frontend-system/building-apps/plugin-conversion', + 'frontend-system/building-apps/migrating', + ], + ), + sidebarElementWithIndex( + { + label: 'Utility APIs', + description: 'Utility APIs for the frontend system.', + }, + [ + 'frontend-system/utility-apis/index', + 'frontend-system/utility-apis/creating', + 'frontend-system/utility-apis/consuming', + 'frontend-system/utility-apis/configuring', + ], + ), + ], + ), + sidebarElementWithIndex( { - type: 'category', - label: 'Building Apps', - items: [ - 'frontend-system/building-apps/index', - 'frontend-system/building-apps/configuring-extensions', - 'frontend-system/building-apps/built-in-extensions', - 'frontend-system/building-apps/plugin-conversion', - 'frontend-system/building-apps/migrating', - ], + label: 'Backstage CLI', + description: 'Command-line interface for Backstage.', }, + [ + 'tooling/cli/overview', + 'tooling/cli/build-system', + 'tooling/cli/commands', + 'tooling/cli/templates', + sidebarElementWithIndex( + { + label: 'Local Development', + description: + 'Guides for local development using Backstage CLI.', + }, + [ + 'tooling/local-dev/linking-local-packages', + 'tooling/local-dev/debugging', + 'tooling/local-dev/profiling', + ], + ), + 'tooling/package-metadata', + ], + ), + sidebarElementWithIndex( { - type: 'category', - label: 'Utility APIs', - items: [ - 'frontend-system/utility-apis/index', - 'frontend-system/utility-apis/creating', - 'frontend-system/utility-apis/consuming', - 'frontend-system/utility-apis/configuring', - ], + label: 'User Interface', + description: 'User interface customization and configuration.', }, - ], - }, + [ + 'conf/user-interface/index', + 'conf/user-interface/logo', + 'conf/user-interface/icons', + 'conf/user-interface/sidebar', + ], + ), + ], + ), + sidebarElementWithIndex( { - 'Backstage CLI': [ - 'tooling/cli/overview', - 'tooling/cli/build-system', - 'tooling/cli/commands', - 'tooling/cli/templates', + label: 'Tutorials', + description: + 'Step-by-step tutorials for various Backstage features and use cases.', + }, + [ + sidebarElementWithIndex( { - type: 'category', - label: 'Local Development', - items: [ - 'tooling/local-dev/linking-local-packages', - 'tooling/local-dev/debugging', - 'tooling/local-dev/profiling', - ], + label: 'Non-technical', + description: 'Non-technical tutorials and guides.', }, - 'tooling/package-metadata', - ], - }, + ['overview/adopting'], + ), + sidebarElementWithIndex( + { + label: 'Technical', + description: 'Technical tutorials and guides.', + }, + [ + 'tutorials/quickstart-app-plugin', + 'tutorials/configuring-plugin-databases', + 'tutorials/manual-knex-rollback', + 'tutorials/switching-sqlite-postgres', + 'tutorials/using-backstage-proxy-within-plugin', + 'tutorials/enable-public-entry', + 'tutorials/setup-opentelemetry', + 'tutorials/integrating-event-driven-updates-with-entity-providers', + 'accessibility/index', + ], + ), + sidebarElementWithIndex( + { + label: 'Migrations', + description: + 'Guides for migrating between different versions and systems.', + }, + [ + 'tutorials/jest30-migration', + 'tutorials/react-router-stable-migration', + 'tutorials/react18-migration', + 'tutorials/package-role-migration', + 'tutorials/migrating-away-from-core', + 'tutorials/yarn-migration', + 'tutorials/migrate-to-mui5', + 'tutorials/auth-service-migration', + 'tutorials/jsx-transform-migration', + ], + ), + ], + ), + sidebarElementWithIndex( + { label: 'FAQ', description: 'Frequently asked questions and answers.' }, + ['faq/index', 'faq/product', 'faq/technical'], + ), + sidebarElementWithIndex( { - type: 'category', - label: 'User Interface', - items: [ - 'conf/user-interface/index', - 'conf/user-interface/logo', - 'conf/user-interface/icons', - 'conf/user-interface/sidebar', - ], + label: 'Contribute', + description: 'Information on contributing to Backstage.', }, - ], - Tutorials: [ - { 'Non-technical': ['overview/adopting'] }, + [ + 'contribute/index', + 'contribute/getting-involved', + 'contribute/project-structure', + ], + ), + sidebarElementWithIndex( { - Technical: [ - 'tutorials/quickstart-app-plugin', - 'tutorials/configuring-plugin-databases', - 'tutorials/manual-knex-rollback', - 'tutorials/switching-sqlite-postgres', - 'tutorials/using-backstage-proxy-within-plugin', - 'tutorials/enable-public-entry', - 'tutorials/setup-opentelemetry', - 'tutorials/integrating-event-driven-updates-with-entity-providers', - 'accessibility/index', - ], + label: 'References', + description: 'Reference materials and documentation.', }, - { - Migrations: [ - 'tutorials/react-router-stable-migration', - 'tutorials/react18-migration', - 'tutorials/package-role-migration', - 'tutorials/migrating-away-from-core', - 'tutorials/yarn-migration', - 'tutorials/migrate-to-mui5', - 'tutorials/auth-service-migration', - 'tutorials/jsx-transform-migration', - ], - }, - ], - FAQ: ['faq/index', 'faq/product', 'faq/technical'], - Contribute: [ - 'contribute/index', - 'contribute/getting-involved', - 'contribute/project-structure', - ], - References: [ - { - 'Designing for Backstage': [ - 'dls/design', - 'dls/component-design-guidelines', - 'dls/contributing-to-storybook', - 'dls/figma', - ], - }, - { - type: 'category', - label: 'Architecture Decision Records (ADRs)', - items: [ - 'architecture-decisions/adrs-overview', - 'architecture-decisions/adrs-adr001', - 'architecture-decisions/adrs-adr002', - 'architecture-decisions/adrs-adr003', - 'architecture-decisions/adrs-adr004', - 'architecture-decisions/adrs-adr005', - 'architecture-decisions/adrs-adr006', - 'architecture-decisions/adrs-adr007', - 'architecture-decisions/adrs-adr008', - 'architecture-decisions/adrs-adr009', - 'architecture-decisions/adrs-adr010', - 'architecture-decisions/adrs-adr011', - 'architecture-decisions/adrs-adr012', - 'architecture-decisions/adrs-adr013', - 'architecture-decisions/adrs-adr014', - ], - }, - 'api/deprecations', - 'references/glossary', - 'api/utility-apis', - 'reference/index', - ], - }, + [ + sidebarElementWithIndex( + { + label: 'Designing for Backstage', + description: + 'Guidelines and resources for designing Backstage components.', + }, + [ + 'dls/design', + 'dls/component-design-guidelines', + 'dls/contributing-to-storybook', + 'dls/figma', + ], + ), + sidebarElementWithIndex( + { + label: 'Architecture Decision Records (ADRs)', + description: + 'Documentation of architectural decisions made in Backstage.', + }, + [ + 'architecture-decisions/adrs-overview', + 'architecture-decisions/adrs-adr001', + 'architecture-decisions/adrs-adr002', + 'architecture-decisions/adrs-adr003', + 'architecture-decisions/adrs-adr004', + 'architecture-decisions/adrs-adr005', + 'architecture-decisions/adrs-adr006', + 'architecture-decisions/adrs-adr007', + 'architecture-decisions/adrs-adr008', + 'architecture-decisions/adrs-adr009', + 'architecture-decisions/adrs-adr010', + 'architecture-decisions/adrs-adr011', + 'architecture-decisions/adrs-adr012', + 'architecture-decisions/adrs-adr013', + 'architecture-decisions/adrs-adr014', + 'architecture-decisions/adrs-adr015', + ], + ), + 'api/deprecations', + 'references/glossary', + 'api/utility-apis', + 'reference/index', + ], + ), + ], releases: { 'Release Notes': releases.map(release => `releases/${release}`), }, diff --git a/microsite/static/img/citrix_sds.png b/microsite/static/img/citrix_sds.png new file mode 100644 index 0000000000..fac58411a1 Binary files /dev/null and b/microsite/static/img/citrix_sds.png differ diff --git a/microsite/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 6badb33181..acceac04e2 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -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.6": + version: 1.15.6 + resolution: "@swc/core-darwin-arm64@npm:1.15.6" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-darwin-x64@npm:1.11.24" +"@swc/core-darwin-x64@npm:1.15.6": + version: 1.15.6 + resolution: "@swc/core-darwin-x64@npm:1.15.6" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.11.24" +"@swc/core-linux-arm-gnueabihf@npm:1.15.6": + version: 1.15.6 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.15.6" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-linux-arm64-gnu@npm:1.11.24" +"@swc/core-linux-arm64-gnu@npm:1.15.6": + version: 1.15.6 + resolution: "@swc/core-linux-arm64-gnu@npm:1.15.6" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-linux-arm64-musl@npm:1.11.24" +"@swc/core-linux-arm64-musl@npm:1.15.6": + version: 1.15.6 + resolution: "@swc/core-linux-arm64-musl@npm:1.15.6" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-linux-x64-gnu@npm:1.11.24" +"@swc/core-linux-x64-gnu@npm:1.15.6": + version: 1.15.6 + resolution: "@swc/core-linux-x64-gnu@npm:1.15.6" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-linux-x64-musl@npm:1.11.24" +"@swc/core-linux-x64-musl@npm:1.15.6": + version: 1.15.6 + resolution: "@swc/core-linux-x64-musl@npm:1.15.6" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-win32-arm64-msvc@npm:1.11.24" +"@swc/core-win32-arm64-msvc@npm:1.15.6": + version: 1.15.6 + resolution: "@swc/core-win32-arm64-msvc@npm:1.15.6" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-win32-ia32-msvc@npm:1.11.24" +"@swc/core-win32-ia32-msvc@npm:1.15.6": + version: 1.15.6 + resolution: "@swc/core-win32-ia32-msvc@npm:1.15.6" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-win32-x64-msvc@npm:1.11.24" +"@swc/core-win32-x64-msvc@npm:1.15.6": + version: 1.15.6 + resolution: "@swc/core-win32-x64-msvc@npm:1.15.6" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@swc/core@npm:^1.3.46": - version: 1.11.24 - resolution: "@swc/core@npm:1.11.24" +"@swc/core@npm:^1.15.6": + version: 1.15.6 + resolution: "@swc/core@npm:1.15.6" 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.6" + "@swc/core-darwin-x64": "npm:1.15.6" + "@swc/core-linux-arm-gnueabihf": "npm:1.15.6" + "@swc/core-linux-arm64-gnu": "npm:1.15.6" + "@swc/core-linux-arm64-musl": "npm:1.15.6" + "@swc/core-linux-x64-gnu": "npm:1.15.6" + "@swc/core-linux-x64-musl": "npm:1.15.6" + "@swc/core-win32-arm64-msvc": "npm:1.15.6" + "@swc/core-win32-ia32-msvc": "npm:1.15.6" + "@swc/core-win32-x64-msvc": "npm:1.15.6" "@swc/counter": "npm:^0.1.3" - "@swc/types": "npm:^0.1.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/d17d1b5e6e81f8c262b668f237b863dd612eaa19fc35e74067b2a27ba3998c0c511ec7f49cd91c02f61fa77dd1b5d7e65d293736f65ec45d6c13989904b1c7eb 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" @@ -4281,8 +4274,7 @@ __metadata: "@docusaurus/tsconfig": "npm:^3.1.1" "@docusaurus/types": "npm:^3.1.1" "@mdx-js/react": "npm:^3.0.0" - "@swc/core": "npm:^1.3.46" - "@types/luxon": "npm:^3.0.0" + "@swc/core": "npm:^1.15.6" "@types/webpack-env": "npm:^1.18.0" clsx: "npm:^2.0.0" docusaurus-plugin-openapi-docs: "npm:^4.3.0" @@ -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 @@ -4729,9 +4721,9 @@ __metadata: linkType: hard "caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001646, caniuse-lite@npm:^1.0.30001688": - version: 1.0.30001695 - resolution: "caniuse-lite@npm:1.0.30001695" - checksum: 10/8107c5e89b86c7a2fd506b93c658ff945c98c6518260c3b28af9f02bd83bf83939696241f0b413545c5b9895c86bcae64c9370388576440e74e9b848f04170d3 + version: 1.0.30001760 + resolution: "caniuse-lite@npm:1.0.30001760" + checksum: 10/ac5c13d00b946c3ace331d25379ed9d85743b1028aba79ae80402cb128b4b491122585144898fefb836d4d9879c13c717a081ae241a00420cbbc7e1b934ec685 languageName: node linkType: hard @@ -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" @@ -8904,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 @@ -10631,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 @@ -10909,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: @@ -11287,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" @@ -11310,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" @@ -12049,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: @@ -12130,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 @@ -13059,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" @@ -13071,7 +13067,7 @@ __metadata: optional: true bin: sass: sass.js - checksum: 10/9a7d1c6be1a9e711a1c561d189b9816aa7715f6d0ec0b2ec181f64163788d0caaf4741924eeadce558720b58b1de0e9b21b9dae6a0d14489c4d2a142d3f3b12e + checksum: 10/e60c214ea93677740c9ddfad55c77fd433255bbfdd9faba137acf1215bed5ba6ad9d83efea81feb87a89283931d01f0435227e3fff37c65c263e0ee05f885328 languageName: node linkType: hard @@ -13199,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" @@ -13239,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: @@ -13286,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 @@ -13449,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: @@ -13696,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" @@ -14119,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 @@ -14226,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 @@ -14470,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 fd9c500b1c..f39c8f1905 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "1.45.0", + "version": "1.46.0", "backstage": { "cli": { "new": { @@ -47,6 +47,7 @@ "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 .", @@ -116,7 +117,6 @@ "csstype@npm:^3.0.2": "3.0.9", "csstype@npm:^3.1.2": "3.0.9", "csstype@npm:^3.1.3": "3.0.9", - "jest-haste-map@^29.7.0": "patch:jest-haste-map@npm%3A29.7.0#./.yarn/patches/jest-haste-map-npm-29.7.0-e3be419eff.patch", "recast@npm:0.23.9>ast-types": "patch:ast-types@npm%3A0.16.1#./.yarn/patches/ast-types-npm-0.16.1-43c4ac4b0d.patch" }, "dependencies": { @@ -137,15 +137,16 @@ "@octokit/rest": "^19.0.3", "@playwright/test": "^1.32.3", "@spotify/eslint-plugin": "^15.0.0", - "@storybook/addon-a11y": "^9.1.7", - "@storybook/addon-docs": "^9.1.7", - "@storybook/addon-links": "^9.1.7", - "@storybook/addon-themes": "^9.1.7", - "@storybook/react-vite": "^9.1.7", + "@storybook/addon-a11y": "^10.1.9", + "@storybook/addon-docs": "^10.1.9", + "@storybook/addon-links": "^10.1.9", + "@storybook/addon-themes": "^10.1.9", + "@storybook/addon-vitest": "^10.1.9", + "@storybook/react-vite": "^10.1.9", "@techdocs/cli": "workspace:*", "@types/cacheable-request": "^8.3.6", "@types/memjs": "^1.3.3", - "@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", @@ -157,6 +158,7 @@ "eslint-plugin-testing-library": "^6.0.0", "fs-extra": "^11.2.0", "husky": "^9.0.0", + "js-yaml": "^4.1.1", "lint-staged": "^15.0.0", "madge": "^8.0.0", "minimist": "^1.2.5", @@ -166,7 +168,7 @@ "shx": "^0.4.0", "sloc": "^0.3.1", "sort-package-json": "^2.8.0", - "storybook": "^9.1.7", + "storybook": "^10.1.10", "ts-morph": "^24.0.0", "typedoc": "^0.28.0", "typescript": "~5.7.0", @@ -174,7 +176,7 @@ }, "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 5143f6deac..cca956654d 100644 --- a/packages/app-defaults/CHANGELOG.md +++ b/packages/app-defaults/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/app-defaults +## 1.7.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/plugin-permission-react@0.4.39 + +## 1.7.3-next.0 + +### Patch Changes + +- 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 diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json index a13f28414d..878e5b505b 100644 --- a/packages/app-defaults/package.json +++ b/packages/app-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/app-defaults", - "version": "1.7.2", + "version": "1.7.3", "description": "Provides the default wiring of a Backstage App", "backstage": { "role": "web-library" diff --git a/packages/app-next-example-plugin/CHANGELOG.md b/packages/app-next-example-plugin/CHANGELOG.md index ee662ff631..ee1e797be4 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 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + +## 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 diff --git a/packages/app-next-example-plugin/package.json b/packages/app-next-example-plugin/package.json index b3db6cac60..f632f00de8 100644 --- a/packages/app-next-example-plugin/package.json +++ b/packages/app-next-example-plugin/package.json @@ -1,6 +1,6 @@ { "name": "app-next-example-plugin", - "version": "0.0.29", + "version": "0.0.30", "description": "Backstage internal example plugin", "backstage": { "role": "frontend-plugin", diff --git a/packages/app-next/CHANGELOG.md b/packages/app-next/CHANGELOG.md index 131819dd8e..8ae5c67cb7 100644 --- a/packages/app-next/CHANGELOG.md +++ b/packages/app-next/CHANGELOG.md @@ -1,5 +1,159 @@ # example-app-next +## 0.0.30 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/cli@0.35.0 + - @backstage/ui@0.10.0 + - @backstage/plugin-catalog-graph@0.5.4 + - @backstage/plugin-api-docs@0.13.2 + - @backstage/plugin-techdocs@1.16.1 + - @backstage/plugin-catalog@1.32.1 + - @backstage/plugin-search@1.5.1 + - @backstage/plugin-app@0.3.3 + - @backstage/plugin-org@0.6.47 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/plugin-scaffolder-react@1.19.4 + - @backstage/core-components@0.18.4 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.31 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-kubernetes@0.12.14 + - @backstage/plugin-search-react@1.10.1 + - @backstage/core-plugin-api@1.12.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24 + - @backstage/plugin-app-visualizer@0.1.26 + - @backstage/plugin-catalog-import@0.13.8 + - @backstage/plugin-notifications@0.5.12 + - @backstage/plugin-user-settings@0.8.30 + - @backstage/plugin-scaffolder@1.35.0 + - @backstage/plugin-home@0.8.15 + - @backstage/theme@0.7.1 + - @backstage/plugin-kubernetes-cluster@0.0.32 + - @backstage/app-defaults@1.7.3 + - @backstage/core-compat-api@0.5.5 + - @backstage/frontend-app-api@0.13.3 + - @backstage/frontend-defaults@0.3.4 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-auth@0.1.3 + - @backstage/plugin-auth-react@0.1.22 + - @backstage/plugin-permission-react@0.4.39 + - @backstage/plugin-signals@0.0.26 + - @backstage/plugin-techdocs-react@1.3.6 + +## 0.0.30-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3-next.1 + - @backstage/cli@0.35.0-next.2 + - @backstage/plugin-catalog-graph@0.5.4-next.1 + - @backstage/plugin-api-docs@0.13.2-next.1 + - @backstage/plugin-techdocs@1.16.1-next.2 + - @backstage/plugin-catalog@1.32.1-next.1 + - @backstage/plugin-search@1.5.1-next.1 + - @backstage/plugin-app@0.3.3-next.1 + - @backstage/plugin-org@0.6.47-next.1 + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-scaffolder-react@1.19.4-next.2 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/plugin-home@0.8.15-next.1 + - @backstage/plugin-scaffolder@1.34.4-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.32-next.2 + - @backstage/app-defaults@1.7.3-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-compat-api@0.5.5-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/frontend-app-api@0.13.3-next.0 + - @backstage/frontend-defaults@0.3.4-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/ui@0.10.0-next.1 + - @backstage/plugin-app-visualizer@0.1.26-next.1 + - @backstage/plugin-auth@0.1.3-next.0 + - @backstage/plugin-auth-react@0.1.22-next.0 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-import@0.13.8-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24-next.1 + - @backstage/plugin-kubernetes@0.12.14-next.1 + - @backstage/plugin-notifications@0.5.12-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-search-react@1.10.1-next.0 + - @backstage/plugin-signals@0.0.26-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.31-next.1 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/plugin-user-settings@0.8.30-next.0 + +## 0.0.30-next.1 + +### Patch Changes + +- 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 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 b5f9bcd7b1..1bb03fb1c5 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", + "version": "0.0.30", "backstage": { "role": "frontend" }, diff --git a/packages/app-next/src/modules/appModuleNav.tsx b/packages/app-next/src/modules/appModuleNav.tsx index aa7d92bd1b..ff89b2d1de 100644 --- a/packages/app-next/src/modules/appModuleNav.tsx +++ b/packages/app-next/src/modules/appModuleNav.tsx @@ -14,7 +14,6 @@ * limitations under the License. */ -import { compatWrapper } from '@backstage/core-compat-api'; import { Link, Sidebar, @@ -106,36 +105,34 @@ export const appModuleNav = createFrontendModule({ extensions: [ NavContentBlueprint.make({ params: { - component: ({ items }) => { - 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 f4a516501b..4de0d448e0 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,147 @@ # example-app +## 0.2.116 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/cli@0.35.0 + - @backstage/ui@0.10.0 + - @backstage/plugin-catalog-graph@0.5.4 + - @backstage/plugin-api-docs@0.13.2 + - @backstage/plugin-techdocs@1.16.1 + - @backstage/plugin-catalog@1.32.1 + - @backstage/plugin-search@1.5.1 + - @backstage/plugin-org@0.6.47 + - @backstage/plugin-scaffolder-react@1.19.4 + - @backstage/core-components@0.18.4 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.31 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-kubernetes@0.12.14 + - @backstage/plugin-search-react@1.10.1 + - @backstage/core-plugin-api@1.12.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24 + - @backstage/plugin-catalog-import@0.13.8 + - @backstage/plugin-notifications@0.5.12 + - @backstage/plugin-user-settings@0.8.30 + - @backstage/plugin-scaffolder@1.35.0 + - @backstage/plugin-devtools@0.1.34 + - @backstage/plugin-home@0.8.15 + - @backstage/theme@0.7.1 + - @backstage/plugin-kubernetes-cluster@0.0.32 + - @backstage/app-defaults@1.7.3 + - @backstage/frontend-app-api@0.13.3 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-auth-react@0.1.22 + - @backstage/plugin-mui-to-bui@0.2.2 + - @backstage/plugin-permission-react@0.4.39 + - @backstage/plugin-signals@0.0.26 + - @backstage/plugin-techdocs-react@1.3.6 + +## 0.2.116-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3-next.1 + - @backstage/cli@0.35.0-next.2 + - @backstage/plugin-catalog-graph@0.5.4-next.1 + - @backstage/plugin-api-docs@0.13.2-next.1 + - @backstage/plugin-techdocs@1.16.1-next.2 + - @backstage/plugin-catalog@1.32.1-next.1 + - @backstage/plugin-search@1.5.1-next.1 + - @backstage/plugin-org@0.6.47-next.1 + - @backstage/plugin-scaffolder-react@1.19.4-next.2 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/plugin-home@0.8.15-next.1 + - @backstage/plugin-scaffolder@1.34.4-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.32-next.2 + - @backstage/app-defaults@1.7.3-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/frontend-app-api@0.13.3-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/ui@0.10.0-next.1 + - @backstage/plugin-auth-react@0.1.22-next.0 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-import@0.13.8-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.24-next.1 + - @backstage/plugin-devtools@0.1.34-next.0 + - @backstage/plugin-kubernetes@0.12.14-next.1 + - @backstage/plugin-mui-to-bui@0.2.2-next.1 + - @backstage/plugin-notifications@0.5.12-next.0 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-search-react@1.10.1-next.0 + - @backstage/plugin-signals@0.0.26-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.31-next.1 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + - @backstage/plugin-user-settings@0.8.30-next.0 + +## 0.2.116-next.1 + +### Patch Changes + +- 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 diff --git a/packages/app/package.json b/packages/app/package.json index 1aac7276d9..e1f55c6114 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "example-app", - "version": "0.2.115", + "version": "0.2.116", "backstage": { "role": "frontend" }, diff --git a/packages/app/src/components/devtools/CustomDevToolsPage.tsx b/packages/app/src/components/devtools/CustomDevToolsPage.tsx index d1c68d9884..0fe17b14c3 100644 --- a/packages/app/src/components/devtools/CustomDevToolsPage.tsx +++ b/packages/app/src/components/devtools/CustomDevToolsPage.tsx @@ -18,6 +18,7 @@ import { ConfigContent, ExternalDependenciesContent, InfoContent, + ScheduledTasksContent, } from '@backstage/plugin-devtools'; import { DevToolsLayout } from '@backstage/plugin-devtools'; import { UnprocessedEntitiesContent } from '@backstage/plugin-catalog-unprocessed-entities'; @@ -31,6 +32,9 @@ const DevToolsPage = () => { + + + , ): 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 965e467501..1c0bf5a0ef 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts @@ -26,6 +26,7 @@ import { } from '@backstage/backend-plugin-api'; import { BackendInitializer } from './BackendInitializer'; import { mockServices } from '@backstage/backend-test-utils'; +import { BackendStartupError } from './BackendStartupError'; const baseFactories = [ mockServices.rootLifecycle.factory(), @@ -806,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); @@ -1289,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 e7480d3beb..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 @@ -38,9 +37,12 @@ import type { InternalServiceFactory } from '../../../backend-plugin-api/src/ser import { ForwardedError, ConflictError, assertError } from '@backstage/errors'; import { DependencyGraph } from '../lib/DependencyGraph'; import { ServiceRegistry } from './ServiceRegistry'; -import { createInitializationLogger } from './createInitializationLogger'; +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; @@ -148,7 +150,7 @@ function createRootInstanceMetadataServiceFactory( } export class BackendInitializer { - #startPromise?: Promise; + #startPromise?: Promise<{ result: BackendStartupResult }>; #stopPromise?: Promise; #registrations = new Array(); #extensionPoints = new Map(); @@ -224,7 +226,7 @@ export class BackendInitializer { } } - async start(): Promise { + async start(): Promise<{ result: BackendStartupResult }> { if (this.#startPromise) { throw new Error('Backend has already started'); } @@ -235,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) { @@ -332,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( @@ -382,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, + ); } }, ); @@ -426,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 @@ -654,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/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 52b9168160..dcd5361493 100644 --- a/packages/backend-defaults/CHANGELOG.md +++ b/packages/backend-defaults/CHANGELOG.md @@ -1,5 +1,124 @@ # @backstage/backend-defaults +## 0.14.0 + +### Minor Changes + +- fa43826: Move `better-sqlite3` from dependencies to peer dependencies +- 2bc4e02: **BREAKING** The correct configuration options for Valkey are now being used. + + These changes are **required** to `app-config.yaml`: + + ```diff + backend: + cache: + store: valkey + connection: ... + client: + - namespace: 'my-app' + - keyPrefixSeparator: ':' + + keyPrefix: 'my-app:' + - clearBatchSize: 1000 + - useUnlink: false + ``` + + In comparison to Redis, Valkey requires the full `keyPrefix` including the separator to be specified instead of separate `namespace` and `keyPrefixSeparator` options. Also, Valkey does not support the `clearBatchSize` and `useUnlink` options. + +### Patch Changes + +- 37fba1d: Added support for Bitbucket Cloud OAuth. This introduces an alternative authentication method using a workspace OAuth consumer, alongside App Passwords (deprecated) and API tokens. OAuth does not require a bot or service account and avoids token expiry issues. + + **BREAKING CHANGES** + + - **@backstage/integration** (`src/bitbucketCloud/core.ts`) + + - `getBitbucketCloudRequestOptions` now returns a `Promise` and **must** be awaited. + + - **@backstage/plugin-scaffolder-backend-module-bitbucket-cloud** (`src/actions/helpers.ts`) + - `getBitbucketClient` now returns a `Promise` and **must** be awaited. + - `getAuthorizationHeader` now returns a `Promise` and **must** be awaited. + + **OAuth usage example** + + ```yaml + integrations: + bitbucketCloud: + - clientId: client-id + clientSecret: client-secret + ``` + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- aa79251: build(deps): bump `node-forge` from 1.3.1 to 1.3.2 +- f96edff: Allow configuration of the `referrerPolicy` +- fb029b6: Updated luxon types +- d9759a1: **BREAKING ALPHA**: The old `instanceMetadataService` has been removed from alpha. Please switch over to using the stable `coreServices.rootInstanceMetadata` and related types instead, available from `@backstage/backend-plugin-api`. +- 847a330: Fix for `jose` types +- 25b560e: Internal change to support new versions of the `logform` library +- 2a0c4b0: Adds a new experimental `RootSystemMetadataService` for tracking the collection of Backstage instances that may be deployed at any one time. It currently offers a single API, `getInstalledPlugins` that returns a list of installed plugins based on config you have set up in `discovery.endpoints` as well as the plugins installed on the instance you're calling the API with. It does not handle wildcard values or fallback values. The intention is for this plugin to provide plugin authors with a simple interface to fetch a trustworthy list of all installed plugins. +- 3016a79: Updated dependency `@types/archiver` to `^7.0.0`. +- 42db6a6: Don't warn when parsing `storeOptions` for `memory` cache +- Updated dependencies + - @backstage/cli-node@0.2.16 + - @backstage/integration@1.19.0 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-events-node@0.4.18 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-app-api@1.4.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/config-loader@1.10.7 + - @backstage/backend-dev-utils@0.1.6 + +## 0.14.0-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- aa79251: build(deps): bump `node-forge` from 1.3.1 to 1.3.2 +- f96edff: Allow configuration of the `referrerPolicy` +- fb029b6: Updated luxon types +- 847a330: Fix for `jose` types +- 25b560e: Internal change to support new versions of the `logform` library +- 2a0c4b0: Adds a new experimental `RootSystemMetadataService` for tracking the collection of Backstage instances that may be deployed at any one time. It currently offers a single API, `getInstalledPlugins` that returns a list of installed plugins based on config you have set up in `discovery.endpoints` as well as the plugins installed on the instance you're calling the API with. It does not handle wildcard values or fallback values. The intention is for this plugin to provide plugin authors with a simple interface to fetch a trustworthy list of all installed plugins. +- 3016a79: Updated dependency `@types/archiver` to `^7.0.0`. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config-loader@1.10.7-next.1 + - @backstage/backend-dev-utils@0.1.6-next.0 + - @backstage/backend-app-api@1.4.0-next.1 + - @backstage/cli-node@0.2.16-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.19 + - @backstage/types@1.2.2 + +## 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 diff --git a/packages/backend-defaults/config.d.ts b/packages/backend-defaults/config.d.ts index f0632a37bb..8095e24660 100644 --- a/packages/backend-defaults/config.d.ts +++ b/packages/backend-defaults/config.d.ts @@ -710,27 +710,9 @@ export interface Config { */ client?: { /** - * Namespace for the current instance. + * Namespace and separator used for prefixing keys. */ - namespace?: string; - /** - * Separator to use between namespace and key. - */ - keyPrefixSeparator?: string; - /** - * Number of keys to delete in a single batch. - */ - clearBatchSize?: number; - /** - * Enable Unlink instead of using Del for clearing keys. This is more performant but may not be supported by all Redis versions. - */ - useUnlink?: boolean; - /** - * Whether to allow clearing all keys when no namespace is set. - * If set to true and no namespace is set, iterate() will return all keys. - * Defaults to `false`. - */ - noNamespaceAffectsAll?: boolean; + keyPrefix?: string; }; /** * An optional Valkey cluster (redis cluster under the hood) configuration. @@ -993,6 +975,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 13d7b26224..9281c5481a 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", + "version": "0.14.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 2ca3525946..c40517863a 100644 --- a/packages/backend-defaults/report-alpha.api.md +++ b/packages/backend-defaults/report-alpha.api.md @@ -5,7 +5,7 @@ ```ts import { ActionsRegistryService } from '@backstage/backend-plugin-api/alpha'; import { ActionsService } from '@backstage/backend-plugin-api/alpha'; -import { InstanceMetadataService } from '@backstage/backend-plugin-api/alpha'; +import { RootSystemMetadataService } from '@backstage/backend-plugin-api/alpha'; import { ServiceFactory } from '@backstage/backend-plugin-api'; // @public (undocumented) @@ -22,10 +22,10 @@ export const actionsServiceFactory: ServiceFactory< 'singleton' >; -// @alpha @deprecated (undocumented) -export const instanceMetadataServiceFactory: ServiceFactory< - InstanceMetadataService, - 'plugin', +// @alpha +export const rootSystemMetadataServiceFactory: ServiceFactory< + RootSystemMetadataService, + 'root', 'singleton' >; diff --git a/packages/backend-defaults/src/CreateBackend.ts b/packages/backend-defaults/src/CreateBackend.ts index 44df59b5f1..9f8116a996 100644 --- a/packages/backend-defaults/src/CreateBackend.ts +++ b/packages/backend-defaults/src/CreateBackend.ts @@ -38,8 +38,8 @@ import { eventsServiceFactory } from '@backstage/plugin-events-node'; import { actionsRegistryServiceFactory, actionsServiceFactory, - instanceMetadataServiceFactory, } from '@backstage/backend-defaults/alpha'; +import { instanceMetadataServiceFactory } from './alpha/entrypoints/instanceMetadata/instanceMetadataServiceFactory'; export const defaultServiceFactories = [ auditorServiceFactory, @@ -66,6 +66,8 @@ 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 index 2368ea74e8..73de60efdd 100644 --- a/packages/backend-defaults/src/alpha/entrypoints/instanceMetadata/instanceMetadataServiceFactory.test.ts +++ b/packages/backend-defaults/src/alpha/entrypoints/instanceMetadata/instanceMetadataServiceFactory.test.ts @@ -18,9 +18,11 @@ import { coreServices, createBackendPlugin, } from '@backstage/backend-plugin-api'; -import { instanceMetadataServiceFactory } from './instanceMetadataServiceFactory'; +import { + instanceMetadataServiceFactory, + instanceMetadataServiceRef, +} from './instanceMetadataServiceFactory'; import { startTestBackend } from '@backstage/backend-test-utils'; -import { instanceMetadataServiceRef } from '@backstage/backend-plugin-api/alpha'; import Router from 'express-promise-router'; import request from 'supertest'; diff --git a/packages/backend-defaults/src/alpha/entrypoints/instanceMetadata/instanceMetadataServiceFactory.ts b/packages/backend-defaults/src/alpha/entrypoints/instanceMetadata/instanceMetadataServiceFactory.ts index bd2d427a59..eb50037511 100644 --- a/packages/backend-defaults/src/alpha/entrypoints/instanceMetadata/instanceMetadataServiceFactory.ts +++ b/packages/backend-defaults/src/alpha/entrypoints/instanceMetadata/instanceMetadataServiceFactory.ts @@ -17,16 +17,46 @@ import { coreServices, createServiceFactory, + createServiceRef, } from '@backstage/backend-plugin-api'; -import { - BackendFeatureMeta, - InstanceMetadataService, - instanceMetadataServiceRef, -} from '@backstage/backend-plugin-api/alpha'; + +/* + * NOTE(freben): We have moved over to coreServices.rootInstanceMetadata. This + * old alpha implementation is kept around for a little longer for backward + * compatibility reasons. + */ /** - * @alpha - * @deprecated use {@link @backstage/backend-plugin-api#coreServices.rootInstanceMetadata} instead + * @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, 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/backend-defaults/src/alpha/entrypoints/rootSystemMetadata/index.ts b/packages/backend-defaults/src/alpha/entrypoints/rootSystemMetadata/index.ts new file mode 100644 index 0000000000..cff4eccdbe --- /dev/null +++ b/packages/backend-defaults/src/alpha/entrypoints/rootSystemMetadata/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { 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 744d5bc552..c17e0e71cb 100644 --- a/packages/backend-defaults/src/alpha/index.ts +++ b/packages/backend-defaults/src/alpha/index.ts @@ -13,6 +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 { instanceMetadataServiceFactory } from './entrypoints/instanceMetadata'; +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/cache/CacheManager.test.ts b/packages/backend-defaults/src/entrypoints/cache/CacheManager.test.ts index a308caa222..323ca881c5 100644 --- a/packages/backend-defaults/src/entrypoints/cache/CacheManager.test.ts +++ b/packages/backend-defaults/src/entrypoints/cache/CacheManager.test.ts @@ -336,11 +336,17 @@ describe('CacheManager store options', () => { it('correctly applies namespace configuration to redis and valkey stores', () => { const testCases = [ - { store: 'redis', namespace: 'test1', separator: ':' }, - { store: 'valkey', namespace: 'test2', separator: '!' }, + { + store: 'redis', + client: { + namespace: 'my-app', + keyPrefixSeparator: ':', + }, + }, + { store: 'valkey', client: { keyPrefix: 'my-app:' } }, ]; - testCases.forEach(({ store, namespace, separator }) => { + testCases.forEach(({ store, client }) => { const manager = CacheManager.fromConfig( mockServices.rootConfig({ data: { @@ -349,10 +355,7 @@ describe('CacheManager store options', () => { store, connection: 'redis://localhost:6379', [store]: { - client: { - namespace, - keyPrefixSeparator: separator, - }, + client, }, }, }, @@ -364,16 +367,16 @@ describe('CacheManager store options', () => { if (store === 'redis') { // eslint-disable-next-line jest/no-conditional-expect - expect(KeyvRedis).toHaveBeenCalledWith('redis://localhost:6379', { - namespace, - keyPrefixSeparator: separator, - }); + expect(KeyvRedis).toHaveBeenCalledWith( + 'redis://localhost:6379', + client, + ); } else if (store === 'valkey') { // eslint-disable-next-line jest/no-conditional-expect - expect(KeyvValkey).toHaveBeenCalledWith('redis://localhost:6379', { - namespace, - keyPrefixSeparator: separator, - }); + expect(KeyvValkey).toHaveBeenCalledWith( + 'redis://localhost:6379', + client, + ); } }); }); @@ -408,21 +411,31 @@ describe('CacheManager store options', () => { expect(result).toBe('testPlugin'); }); - it('returns pluginId when store options have no namespace', () => { - const storeOptions = { - client: { - keyPrefixSeparator: ':', - }, - }; - const result = (CacheManager as any).constructNamespace( - 'testPlugin', - storeOptions, - ); - expect(result).toBe('testPlugin'); - }); + it.each([ + { + type: 'redis', + field: 'namespace', + client: { keyPrefixSeparator: ':' }, + }, + { type: 'valkey', field: 'keyPrefix', client: {} }, + ])( + 'returns pluginId when store options have no $field for $type', + ({ type, client }) => { + const storeOptions = { + type, + client, + }; + const result = (CacheManager as any).constructNamespace( + 'testPlugin', + storeOptions, + ); + expect(result).toBe('testPlugin'); + }, + ); - it('combines namespace and pluginId with default separator', () => { + it('combines namespace and pluginId with default separator for redis', () => { const storeOptions = { + type: 'redis', client: { namespace: 'my-app', keyPrefixSeparator: ':', @@ -435,8 +448,9 @@ describe('CacheManager store options', () => { expect(result).toBe('my-app:testPlugin'); }); - it('combines namespace and pluginId with custom separator', () => { + it('combines namespace and pluginId with custom separator for redis', () => { const storeOptions = { + type: 'redis', client: { namespace: 'my-app', keyPrefixSeparator: '-', @@ -449,8 +463,9 @@ describe('CacheManager store options', () => { expect(result).toBe('my-app-testPlugin'); }); - it('uses default separator when keyPrefixSeparator is not provided', () => { + it('uses default separator when keyPrefixSeparator is not provided for redis', () => { const storeOptions = { + type: 'redis', client: { namespace: 'my-app', }, @@ -462,6 +477,20 @@ describe('CacheManager store options', () => { expect(result).toBe('my-app:testPlugin'); }); + it('uses keyPrefix for valkey', () => { + const storeOptions = { + type: 'valkey', + client: { + keyPrefix: 'my-app:', + }, + }; + const result = (CacheManager as any).constructNamespace( + 'testPlugin', + storeOptions, + ); + expect(result).toBe('my-app:testPlugin'); + }); + it('handles empty namespace by falling back to pluginId', () => { const storeOptions = { client: { diff --git a/packages/backend-defaults/src/entrypoints/cache/CacheManager.ts b/packages/backend-defaults/src/entrypoints/cache/CacheManager.ts index 06b2b0c57b..38c0c761ed 100644 --- a/packages/backend-defaults/src/entrypoints/cache/CacheManager.ts +++ b/packages/backend-defaults/src/entrypoints/cache/CacheManager.ts @@ -29,6 +29,7 @@ import { RedisCacheStoreOptions, InfinispanClientBehaviorOptions, InfinispanServerConfig, + ValkeyCacheStoreOptions, } from './types'; import { InfinispanOptionsMapper } from './providers/infinispan/InfinispanOptionsMapper'; import { durationToMilliseconds } from '@backstage/types'; @@ -134,43 +135,38 @@ export class CacheManager { ): CacheStoreOptions | undefined { const storeConfigPath = `backend.cache.${store}`; - if (!config.has(storeConfigPath)) { + if (store !== 'memory' && !config.has(storeConfigPath)) { logger?.warn( `No configuration found for cache store '${store}' at '${storeConfigPath}'.`, ); } - if (store === 'redis' || store === 'valkey') { - return CacheManager.parseRedisOptions( - store, - storeConfigPath, - config, - logger, - ); + switch (store) { + case 'redis': + return CacheManager.parseRedisOptions(storeConfigPath, config, logger); + case 'valkey': + return CacheManager.parseValkeyOptions(storeConfigPath, config, logger); + case 'infinispan': + return InfinispanOptionsMapper.parseInfinispanOptions( + storeConfigPath, + config, + logger, + ); + default: + return undefined; } - - if (store === 'infinispan') { - return InfinispanOptionsMapper.parseInfinispanOptions( - storeConfigPath, - config, - logger, - ); - } - - return undefined; } /** * Parse Redis-specific options from configuration. */ private static parseRedisOptions( - store: string, storeConfigPath: string, config: RootConfigService, logger?: LoggerService, ): RedisCacheStoreOptions { const redisOptions: RedisCacheStoreOptions = { - type: store as 'redis' | 'valkey', + type: 'redis', }; const redisConfig = @@ -213,6 +209,51 @@ export class CacheManager { return redisOptions; } + /** + * Parse Valkey-specific options from configuration. + */ + private static parseValkeyOptions( + storeConfigPath: string, + config: RootConfigService, + logger?: LoggerService, + ): ValkeyCacheStoreOptions { + const valkeyOptions: ValkeyCacheStoreOptions = { + type: 'valkey', + }; + + const valkeyConfig = + config.getOptionalConfig(storeConfigPath) ?? new ConfigReader({}); + + valkeyOptions.client = { + keyPrefix: valkeyConfig.getOptionalString('client.keyPrefix'), + }; + + if (valkeyConfig.has('cluster')) { + const clusterConfig = valkeyConfig.getConfig('cluster'); + + if (!clusterConfig.has('rootNodes')) { + logger?.warn( + `Valkey cluster config has no 'rootNodes' key, defaulting to non-clustered mode`, + ); + return valkeyOptions; + } + + valkeyOptions.cluster = { + rootNodes: clusterConfig.get('rootNodes'), + defaults: clusterConfig.getOptional('defaults'), + minimizeConnections: clusterConfig.getOptionalBoolean( + 'minimizeConnections', + ), + useReplicas: clusterConfig.getOptionalBoolean('useReplicas'), + maxCommandRedirections: clusterConfig.getOptionalNumber( + 'maxCommandRedirections', + ), + }; + } + + return valkeyOptions; + } + /** * Construct the full namespace based on the options and pluginId. * @@ -222,13 +263,23 @@ export class CacheManager { */ private static constructNamespace( pluginId: string, - storeOptions: RedisCacheStoreOptions | undefined, + storeOptions: RedisCacheStoreOptions | ValkeyCacheStoreOptions | undefined, ): string { - const prefix = storeOptions?.client?.namespace - ? `${storeOptions.client.namespace}${ - storeOptions.client.keyPrefixSeparator ?? ':' - }` - : ''; + let prefix: string; + switch (storeOptions?.type) { + case 'redis': + prefix = storeOptions?.client?.namespace + ? `${storeOptions.client.namespace}${ + storeOptions.client.keyPrefixSeparator ?? ':' + }` + : ''; + break; + case 'valkey': + prefix = storeOptions.client?.keyPrefix ?? ''; + break; + default: + prefix = ''; + } return `${prefix}${pluginId}`; } @@ -317,7 +368,9 @@ export class CacheManager { private createValkeyStoreFactory(): StoreFactory { const KeyvValkey = require('@keyv/valkey').default; - const { createCluster } = require('@keyv/valkey'); + // `@keyv/valkey` doesn't export a `createCluster` function, but is compatible with the one from `@keyv/redis` + // See https://keyv.org/docs/storage-adapters/valkey + const { createCluster } = require('@keyv/redis'); const stores: Record = {}; return (pluginId, defaultTtl) => { @@ -327,9 +380,7 @@ export class CacheManager { ); } if (!stores[pluginId]) { - const valkeyOptions = this.storeOptions?.client || { - keyPrefixSeparator: ':', - }; + const valkeyOptions = this.storeOptions?.client; if (this.storeOptions?.cluster) { // Create a Valkey cluster (Redis cluster under the hood) const cluster = createCluster(this.storeOptions?.cluster); diff --git a/packages/backend-defaults/src/entrypoints/cache/types.ts b/packages/backend-defaults/src/entrypoints/cache/types.ts index 522ab2ac3e..60e32e8618 100644 --- a/packages/backend-defaults/src/entrypoints/cache/types.ts +++ b/packages/backend-defaults/src/entrypoints/cache/types.ts @@ -17,6 +17,7 @@ import { LoggerService } from '@backstage/backend-plugin-api'; import { HumanDuration, durationToMilliseconds } from '@backstage/types'; import { RedisClusterOptions, KeyvRedisOptions } from '@keyv/redis'; +import { KeyvValkeyOptions } from '@keyv/valkey'; /** * Options for Redis cache store. @@ -24,11 +25,22 @@ import { RedisClusterOptions, KeyvRedisOptions } from '@keyv/redis'; * @public */ export type RedisCacheStoreOptions = { - type: 'redis' | 'valkey'; + type: 'redis'; client?: KeyvRedisOptions; cluster?: RedisClusterOptions; }; +/** + * Options for Valkey cache store. + * + * @public + */ +export type ValkeyCacheStoreOptions = { + type: 'valkey'; + client?: KeyvValkeyOptions; + cluster?: RedisClusterOptions; +}; + /** * Union type of all cache store options. * @@ -36,6 +48,7 @@ export type RedisCacheStoreOptions = { */ export type CacheStoreOptions = | RedisCacheStoreOptions + | ValkeyCacheStoreOptions | InfinispanCacheStoreOptions; /** diff --git a/packages/backend-defaults/src/entrypoints/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/rootHealth/rootHealthServiceFactory.test.ts b/packages/backend-defaults/src/entrypoints/rootHealth/rootHealthServiceFactory.test.ts index ca9085273c..4a5db18f6f 100644 --- a/packages/backend-defaults/src/entrypoints/rootHealth/rootHealthServiceFactory.test.ts +++ b/packages/backend-defaults/src/entrypoints/rootHealth/rootHealthServiceFactory.test.ts @@ -69,7 +69,7 @@ describe('DefaultRootHealthService', () => { await expect(service.getReadiness()).resolves.toEqual({ status: 503, payload: { - message: 'Backend is shuttting down', + message: 'Backend is shutting down', status: 'error', }, }); diff --git a/packages/backend-defaults/src/entrypoints/rootHealth/rootHealthServiceFactory.ts b/packages/backend-defaults/src/entrypoints/rootHealth/rootHealthServiceFactory.ts index e7255610bc..52812fc6b1 100644 --- a/packages/backend-defaults/src/entrypoints/rootHealth/rootHealthServiceFactory.ts +++ b/packages/backend-defaults/src/entrypoints/rootHealth/rootHealthServiceFactory.ts @@ -50,7 +50,7 @@ export class DefaultRootHealthService implements RootHealthService { if (this.#state === 'down') { return { status: 503, - payload: { message: 'Backend is shuttting down', status: 'error' }, + payload: { message: 'Backend is shutting down', status: 'error' }, }; } diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/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/rootHttpRouter/rootHttpRouterServiceFactory.test.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.test.ts index 8f8d1b7656..e9982bc589 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.test.ts +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.test.ts @@ -285,7 +285,7 @@ describe('rootHttpRouterServiceFactory', () => { // Immediately start failing the readiness health check await request(app).get('/.backstage/health/v1/readiness').expect(503, { - message: 'Backend is shuttting down', + message: 'Backend is shutting down', status: 'error', }); @@ -301,7 +301,7 @@ describe('rootHttpRouterServiceFactory', () => { .expect(200, { status: 'ok' }); await request(app).get('/.backstage/health/v1/readiness').expect(503, { - message: 'Backend is shuttting down', + message: 'Backend is shutting down', status: 'error', }); @@ -312,7 +312,7 @@ describe('rootHttpRouterServiceFactory', () => { .expect(200, { status: 'ok' }); await request(app).get('/.backstage/health/v1/readiness').expect(503, { - message: 'Backend is shuttting down', + message: 'Backend is shutting down', status: 'error', }); diff --git a/packages/backend-defaults/src/entrypoints/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 ff6ec3bb27..73302d37e5 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/BitbucketCloudUrlReader.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/BitbucketCloudUrlReader.ts @@ -68,7 +68,15 @@ export class BitbucketCloudUrlReader implements UrlReaderService { ) { this.integration = integration; this.deps = deps; - const { host, username, appPassword, token } = integration.config; + const { host, username, appPassword, token, clientId, clientSecret } = + integration.config; + + // Validate: OAuth requires both clientId and clientSecret + if ((clientId && !clientSecret) || (clientSecret && !clientId)) { + throw new Error( + `Bitbucket Cloud integration has incomplete OAuth configuration. Both clientId and clientSecret are required.`, + ); + } if (username && !token && !appPassword) { throw new Error( @@ -91,7 +99,7 @@ export class BitbucketCloudUrlReader implements UrlReaderService { url, this.integration.config, ); - const requestOptions = getBitbucketCloudRequestOptions( + const requestOptions = await getBitbucketCloudRequestOptions( this.integration.config, ); @@ -149,7 +157,7 @@ export class BitbucketCloudUrlReader implements UrlReaderService { ); const archiveResponse = await fetch( downloadUrl, - getBitbucketCloudRequestOptions(this.integration.config), + await getBitbucketCloudRequestOptions(this.integration.config), ); if (!archiveResponse.ok) { const message = `Failed to read tree from ${url}, ${archiveResponse.status} ${archiveResponse.statusText}`; @@ -250,7 +258,7 @@ export class BitbucketCloudUrlReader implements UrlReaderService { const commitsResponse = await fetch( commitsApiUrl, - getBitbucketCloudRequestOptions(this.integration.config), + await getBitbucketCloudRequestOptions(this.integration.config), ); if (!commitsResponse.ok) { const message = `Failed to retrieve commits from ${commitsApiUrl}, ${commitsResponse.status} ${commitsResponse.statusText}`; diff --git a/packages/backend-dev-utils/CHANGELOG.md b/packages/backend-dev-utils/CHANGELOG.md index 81686fb493..d5884588fb 100644 --- a/packages/backend-dev-utils/CHANGELOG.md +++ b/packages/backend-dev-utils/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/backend-dev-utils +## 0.1.6 + +### Patch Changes + +- 2bae83a: Internal update for Node.js v24 support. + +## 0.1.6-next.0 + +### Patch Changes + +- 2bae83a: Internal update for Node.js v24 support. + ## 0.1.5 ### Patch Changes diff --git a/packages/backend-dev-utils/package.json b/packages/backend-dev-utils/package.json index 4d22c5010f..ba46f4b36e 100644 --- a/packages/backend-dev-utils/package.json +++ b/packages/backend-dev-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-dev-utils", - "version": "0.1.5", + "version": "0.1.6", "backstage": { "role": "node-library" }, diff --git a/packages/backend-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 aaa91feebe..4563cb8381 100644 --- a/packages/backend-dynamic-feature-service/CHANGELOG.md +++ b/packages/backend-dynamic-feature-service/CHANGELOG.md @@ -1,5 +1,77 @@ # @backstage/backend-dynamic-feature-service +## 0.7.7 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/cli-node@0.2.16 + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-defaults@0.14.0 + - @backstage/backend-openapi-utils@0.6.4 + - @backstage/plugin-app-node@0.1.40 + - @backstage/plugin-catalog-backend@3.3.0 + - @backstage/plugin-events-backend@0.5.9 + - @backstage/plugin-events-node@0.4.18 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/cli-common@0.1.16 + - @backstage/config-loader@1.10.7 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.7.7-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/backend-openapi-utils@0.6.4-next.1 + - @backstage/plugin-app-node@0.1.40-next.1 + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-catalog-backend@3.2.1-next.1 + - @backstage/plugin-events-backend@0.5.9-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config-loader@1.10.7-next.1 + - @backstage/cli-common@0.1.16-next.2 + - @backstage/cli-node@0.2.16-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + - @backstage/plugin-search-backend-node@1.4.0-next.1 + - @backstage/plugin-search-common@1.2.21 + +## 0.7.7-next.0 + +### Patch Changes + +- 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 diff --git a/packages/backend-dynamic-feature-service/package.json b/packages/backend-dynamic-feature-service/package.json index 39d59c679f..418a8c804d 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", + "version": "0.7.7", "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 dc84ed4c93..9a741882b0 100644 --- a/packages/backend-openapi-utils/CHANGELOG.md +++ b/packages/backend-openapi-utils/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/backend-openapi-utils +## 0.6.4 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + +## 0.6.4-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.6.4-next.0 + +### Patch Changes + +- 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 diff --git a/packages/backend-openapi-utils/package.json b/packages/backend-openapi-utils/package.json index 48834d3f26..987c3189fa 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", + "version": "0.6.4", "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-openapi-utils/src/stub.ts b/packages/backend-openapi-utils/src/stub.ts index 8ab00c4df4..717e2ae19b 100644 --- a/packages/backend-openapi-utils/src/stub.ts +++ b/packages/backend-openapi-utils/src/stub.ts @@ -31,13 +31,6 @@ import { middleware as OpenApiValidator } from 'express-openapi-validator'; import { OPENAPI_SPEC_ROUTE } from './constants'; import { isErrorResult, merge } from 'openapi-merge'; -type PropertyOverrideRequest = Request & { - [key: symbol]: string; -}; - -const baseUrlSymbol = Symbol(); -const originalUrlSymbol = Symbol(); - function validatorErrorTransformer(): ErrorRequestHandler { return (error: Error, _: Request, _2: Response, next: NextFunction) => { next(new InputError(error.message)); @@ -75,30 +68,6 @@ function createRouterWithValidation( const router = PromiseRouter(); router.use(options?.middleware || getDefaultRouterMiddleware()); - /** - * Middleware to setup the routing for OpenApiValidator. OpenApiValidator expects `req.originalUrl` - * and `req.baseUrl` to be the full path. We adjust them here to basically be nothing and then - * revive the old values in the last function in this method. We could instead update `req.path` - * but that might affect the routing and I'd rather not. - * - * TODO: I opened https://github.com/cdimascio/express-openapi-validator/issues/843 - * to track this on the middleware side, but there was a similar ticket, https://github.com/cdimascio/express-openapi-validator/issues/113 - * that has had minimal activity. If that changes, update this to use a new option on their side. - */ - router.use((req: Request, _, next) => { - /** - * Express typings are weird. They don't recognize PropertyOverrideRequest as a valid - * Request child and try to overload as PathParams. Just cast it here, since we know - * what we're doing. - */ - const customRequest = req as PropertyOverrideRequest; - customRequest[baseUrlSymbol] = customRequest.baseUrl; - customRequest.baseUrl = ''; - customRequest[originalUrlSymbol] = customRequest.originalUrl; - customRequest.originalUrl = customRequest.url; - next(); - }); - // TODO: Handle errors by converting from OpenApiValidator errors to known @backstage/errors errors. router.use( OpenApiValidator({ @@ -106,6 +75,7 @@ function createRouterWithValidation( coerceTypes: false, allowUnknownQueryParameters: false, }, + useRequestUrl: true, ignoreUndocumented: true, validateResponses: false, ...options?.validatorOptions, @@ -113,19 +83,6 @@ function createRouterWithValidation( }), ); - /** - * Revert `req.baseUrl` and `req.originalUrl` changes. This ensures that any further usage - * of these variables will be unchanged. - */ - router.use((req: Request, _, next) => { - const customRequest = req as PropertyOverrideRequest; - customRequest.baseUrl = customRequest[baseUrlSymbol]; - customRequest.originalUrl = customRequest[originalUrlSymbol]; - delete customRequest[baseUrlSymbol]; - delete customRequest[originalUrlSymbol]; - next(); - }); - // Any errors from the middleware get through here. router.use(validatorErrorTransformer()); diff --git a/packages/backend-plugin-api/CHANGELOG.md b/packages/backend-plugin-api/CHANGELOG.md index f9e6fb470f..83d63fa97a 100644 --- a/packages/backend-plugin-api/CHANGELOG.md +++ b/packages/backend-plugin-api/CHANGELOG.md @@ -1,5 +1,50 @@ # @backstage/backend-plugin-api +## 1.6.0 + +### Minor Changes + +- 2a0c4b0: Adds a new experimental `RootSystemMetadataService` for tracking the collection of Backstage instances that may be deployed at any one time. It currently offers a single API, `getInstalledPlugins` that returns a list of installed plugins based on config you have set up in `discovery.endpoints` as well as the plugins installed on the instance you're calling the API with. It does not handle wildcard values or fallback values. The intention is for this plugin to provide plugin authors with a simple interface to fetch a trustworthy list of all installed plugins. + +### Patch Changes + +- d9759a1: **BREAKING ALPHA**: The old `instanceMetadataService` has been removed from alpha. Please switch over to using the stable `coreServices.rootInstanceMetadata` and related types instead, available from `@backstage/backend-plugin-api`. +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/cli-common@0.1.16 + +## 1.6.0-next.1 + +### Minor Changes + +- 2a0c4b0: Adds a new experimental `RootSystemMetadataService` for tracking the collection of Backstage instances that may be deployed at any one time. It currently offers a single API, `getInstalledPlugins` that returns a list of installed plugins based on config you have set up in `discovery.endpoints` as well as the plugins installed on the instance you're calling the API with. It does not handle wildcard values or fallback values. The intention is for this plugin to provide plugin authors with a simple interface to fetch a trustworthy list of all installed plugins. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/cli-common@0.1.16-next.2 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + +## 1.5.1-next.0 + +### Patch Changes + +- 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 diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index 1095d18a9c..4867fcd0a4 100644 --- a/packages/backend-plugin-api/package.json +++ b/packages/backend-plugin-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-plugin-api", - "version": "1.5.0", + "version": "1.6.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/src/alpha/RootSystemMetadataService.ts b/packages/backend-plugin-api/src/alpha/RootSystemMetadataService.ts new file mode 100644 index 0000000000..67cc43f8af --- /dev/null +++ b/packages/backend-plugin-api/src/alpha/RootSystemMetadataService.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. + */ + +/** @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-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md index ed13734949..876aac6137 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,52 @@ # @backstage/backend-test-utils +## 1.10.2 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 8be23a4: Switched `textextensions` dependency for `text-extensions`. +- 5a737e1: Fix PostgreSQL 18 `TestDatabases` by pinning the data directory +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-defaults@0.14.0 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-app-api@1.4.0 + - @backstage/backend-plugin-api@1.6.0 + +## 1.10.2-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 8be23a4: Switched `textextensions` dependency for `text-extensions`. +- 5a737e1: Fix PostgreSQL 18 `TestDatabases` by pinning the data directory +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/backend-app-api@1.4.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + +## 1.10.1-next.0 + +### Patch Changes + +- 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 diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index 61ae7dbedb..1051119b20 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", + "version": "1.10.2", "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/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/CHANGELOG.md b/packages/backend/CHANGELOG.md index 0e7e6c3f24..c287d55ddd 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,129 @@ # example-backend +## 0.0.45 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-mcp-actions-backend@0.1.6 + - @backstage/plugin-scaffolder-backend@3.1.0 + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-defaults@0.14.0 + - @backstage/plugin-app-backend@0.5.9 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.15 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.3 + - @backstage/plugin-auth-backend@0.25.7 + - @backstage/plugin-catalog-backend@3.3.0 + - @backstage/plugin-devtools-backend@0.5.12 + - @backstage/plugin-events-backend@0.5.9 + - @backstage/plugin-kubernetes-backend@0.21.0 + - @backstage/plugin-notifications-backend@0.6.1 + - @backstage/plugin-permission-backend@0.7.7 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/plugin-proxy-backend@0.6.9 + - @backstage/plugin-search-backend@2.0.9 + - @backstage/plugin-signals-backend@0.3.11 + - @backstage/plugin-techdocs-backend@2.1.3 + - @backstage/plugin-auth-backend-module-github-provider@0.4.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-backend-module-github@0.9.3 + - @backstage/plugin-search-backend-module-explore@0.3.10 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.9 + - @backstage/plugin-catalog-backend-module-openapi@0.2.17 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.7 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.7 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.15 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.17 + - @backstage/plugin-search-backend-module-catalog@0.3.11 + - @backstage/plugin-search-backend-module-elasticsearch@1.7.9 + - @backstage/plugin-search-backend-module-techdocs@0.4.9 + +## 0.0.45-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-mcp-actions-backend@0.1.6-next.1 + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/plugin-app-backend@0.5.9-next.1 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.15-next.1 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.3-next.1 + - @backstage/plugin-auth-backend@0.25.7-next.1 + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-catalog-backend@3.2.1-next.1 + - @backstage/plugin-devtools-backend@0.5.12-next.1 + - @backstage/plugin-events-backend@0.5.9-next.1 + - @backstage/plugin-kubernetes-backend@0.21.0-next.2 + - @backstage/plugin-notifications-backend@0.6.1-next.1 + - @backstage/plugin-permission-backend@0.7.7-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/plugin-proxy-backend@0.6.9-next.1 + - @backstage/plugin-scaffolder-backend@3.1.0-next.1 + - @backstage/plugin-search-backend@2.0.9-next.1 + - @backstage/plugin-signals-backend@0.3.11-next.1 + - @backstage/plugin-techdocs-backend@2.1.3-next.2 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/plugin-search-backend-module-explore@0.3.10-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-auth-backend-module-github-provider@0.3.10-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.9-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.2.17-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.7-next.2 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.7-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.15-next.1 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-scaffolder-backend-module-github@0.9.3-next.1 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.17-next.1 + - @backstage/plugin-search-backend-module-catalog@0.3.11-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.7.9-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.4.9-next.1 + - @backstage/plugin-search-backend-node@1.4.0-next.1 + +## 0.0.45-next.0 + +### Patch Changes + +- 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 diff --git a/packages/backend/package.json b/packages/backend/package.json index a3db44d8bf..553cb21e67 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.0.44", + "version": "0.0.45", "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 026fc6fd02..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'; - -// Example usage of the instance metadata service to log the installed plugins. -export default createBackendPlugin({ - pluginId: 'instance-metadata-logging', - register(env) { - env.registerInit({ - deps: { - instanceMetadata: coreServices.rootInstanceMetadata, - logger: coreServices.logger, - }, - async init({ instanceMetadata, logger }) { - const plugins = await instanceMetadata.getInstalledPlugins(); - logger.info( - `Installed plugins on this instance: ${plugins - .map(e => e.pluginId) - .join(', ')}`, - ); - }, - }); - }, -}); diff --git a/packages/cli-common/CHANGELOG.md b/packages/cli-common/CHANGELOG.md index 4a7880688d..a8e4872744 100644 --- a/packages/cli-common/CHANGELOG.md +++ b/packages/cli-common/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/cli-common +## 0.1.16 + +### Patch Changes + +- 5cfb2a4: Added new `run`, `runOutput`, and `runCheck` utilities to help run child processes in a safe and portable way. +- c8c2329: Add proxy configuration from env-vars to create-app tasks +- 2bae83a: Bumped dev dependencies `@types/node` + +## 0.1.16-next.2 + +### Patch Changes + +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/errors@1.2.7 + +## 0.1.16-next.1 + +### Patch Changes + +- 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..0ab0a9f1fd 100644 --- a/packages/cli-common/package.json +++ b/packages/cli-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/cli-common", - "version": "0.1.15", + "version": "0.1.16", "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 060289b753..bdacd69297 100644 --- a/packages/cli-node/CHANGELOG.md +++ b/packages/cli-node/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/cli-node +## 0.2.16 + +### Patch Changes + +- 4e8c726: Updated to use new utilities from `@backstage/cli-common`. +- Updated dependencies + - @backstage/cli-common@0.1.16 + +## 0.2.16-next.1 + +### Patch Changes + +- 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 diff --git a/packages/cli-node/package.json b/packages/cli-node/package.json index ea7be7b267..52242bdd48 100644 --- a/packages/cli-node/package.json +++ b/packages/cli-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/cli-node", - "version": "0.2.15", + "version": "0.2.16", "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 9b71f86074..35392ff03c 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,119 @@ # @backstage/cli +## 0.35.0 + +### Minor Changes + +- f6f22a9: Provide `--no-node-snapshot` by default when running the `package start` or `package test`. You can disable this behavior by providing `NODE_OPTIONS='--node-snapshot'`. +- f8dff94: Switched the default module resolution to `bundler` and the `module` setting to `ES2020`. + + You may need to bump some dependencies as part of this change and fix imports in code. The most common source of this is that type checking will now consider the `exports` field in `package.json` when resolving imports. This in turn can break older versions of packages that had incompatible `exports` fields. Generally these issues will have already been fixed in the upstream packages. + + You might be tempted to use `--skipLibCheck` to hide issues due to this change, but it will weaken the type safety of your project. If you run into a large number of issues and want to keep the old behavior, you can reset the `moduleResolution` and `module` settings your own `tsconfig.json` file to `node` and `ESNext` respectively. But keep in mind that the `node` option will be removed in future versions of TypeScript. + + A future version of Backstage will make these new settings mandatory, as we move to rely on the `exports` field for type resolution in packages, rather than the `typesVersions` field. + +- cd0b8a1: **BREAKING**: `jest` is now a peer dependency. If you run tests using Backstage CLI, you must add Jest and its environment dependencies as `devDependencies` in your project. + + You can choose to install either Jest 29 or Jest 30. The built-in Jest version before this change was Jest 29, however, we recommend that you switch to Jest 30. Upgrading will solve the `Could not parse CSS stylesheet` errors, allow you to use MSW v2 in web packages, and ensure that you remain compatible with future versions of the Backstage CLI. Support for Jest 29 is temporary, with the purpose of allowing you to upgrade at your own pace, but it will eventually be removed. + + - **Jest 29**: Install `jest@^29` and `jest-environment-jsdom@^29`. No migration needed, but you may see `Could not parse CSS stylesheet` warnings/errors when testing components from `@backstage/ui` or other packages using CSS `@layer` declarations. + - **Jest 30**: Install `jest@^30`, `@jest/environment-jsdom-abstract@^30`, and `jsdom@^27`. Fixes the stylesheet parsing warnings/errors, but requires migration steps. + + See the [Jest 30 migration guide](https://backstage.io/docs/tutorials/jest30-migration) for detailed migration instructions. + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- e7db290: Add missing peer/dev dependencies to the frontend plugin template. + + `react-dom` was not declared as a peer dependency, causing module resolution + errors when generating plugins outside a Backstage monorepo. This adds + `react-dom` to `peerDependencies` (for consuming apps) and `devDependencies` + (for local development). `react-router-dom` is also added to `peerDependencies` (for consuming apps) and `devDependencies` + to support routing during plugin development. + + Fixes: + + - Module not found: Can't resolve 'react-dom' + - Module not found: Can't resolve 'react-router-dom' + +- 1226647: Updated dependency `esbuild` to `^0.27.0`. +- f89a074: Updated dependency `@pmmmwh/react-refresh-webpack-plugin` to `^0.6.0`. +- 2b81751: Updated dependency `webpack` to `~5.103.0`. +- fafd9e1: Fixed internal usage of `yargs`. +- c8c2329: Add proxy configuration from env-vars to create-app tasks +- 2bae83a: Switched compilation target to ES2022 in order to match the new set of supported Node.js versions, which are 22 and 24. + + The TypeScript compilation target has been set to ES2022, because setting it to a higher target will break projects on older TypeScript versions. If you use a newer TypeScript version in your own project, you can bump `compilerOptions.target` to ES2023 or ES2024 in your own `tsconfig.json` file. + +- 7fbac5c: Updated to use new utilities from `@backstage/cli-common`. +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/cli-node@0.2.16 + - @backstage/integration@1.19.0 + - @backstage/cli-common@0.1.16 + - @backstage/config-loader@1.10.7 + +## 0.35.0-next.2 + +### Minor Changes + +- f8dff94: Switched the default module resolution to `bundler` and the `module` setting to `ES2020`. + + You may need to bump some dependencies as part of this change and fix imports in code. The most common source of this is that type checking will now consider the `exports` field in `package.json` when resolving imports. This in turn can break older versions of packages that had incompatible `exports` fields. Generally these issues will have already been fixed in the upstream packages. + + You might be tempted to use `--skipLibCheck` to hide issues due to this change, but it will weaken the type safety of your project. If you run into a large number of issues and want to keep the old behavior, you can reset the `moduleResolution` and `module` settings your own `tsconfig.json` file to `node` and `ESNext` respectively. But keep in mind that the `node` option will be removed in future versions of TypeScript. + + A future version of Backstage will make these new settings mandatory, as we move to rely on the `exports` field for type resolution in packages, rather than the `typesVersions` field. + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 1226647: Updated dependency `esbuild` to `^0.27.0`. +- f89a074: Updated dependency `@pmmmwh/react-refresh-webpack-plugin` to `^0.6.0`. +- 2b81751: Updated dependency `webpack` to `~5.103.0`. +- fafd9e1: Fixed internal usage of `yargs`. +- 2bae83a: Switched ECMAScript version to ES2023. +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/config-loader@1.10.7-next.1 + - @backstage/cli-common@0.1.16-next.2 + - @backstage/catalog-model@1.7.6 + - @backstage/cli-node@0.2.16-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/eslint-plugin@0.2.0 + - @backstage/release-manifests@0.0.13 + - @backstage/types@1.2.2 + +## 0.34.6-next.1 + +### Patch Changes + +- 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 diff --git a/packages/cli/cli-report.md b/packages/cli/cli-report.md index a8df5242ad..26bd89e909 100644 --- a/packages/cli/cli-report.md +++ b/packages/cli/cli-report.md @@ -324,7 +324,7 @@ Options: ### `backstage-cli package test` ``` -Usage: backstage-cli [--config=] [TestPathPattern] +Usage: backstage-cli [--config=] [TestPathPatterns] Options: --all @@ -349,7 +349,6 @@ Options: --debug --detectLeaks --detectOpenHandles - --env --errorOnDeprecated --filter --findRelatedTests @@ -359,7 +358,6 @@ Options: --globals --haste --ignoreProjects - --init --injectGlobals --json --lastCommit @@ -400,13 +398,13 @@ Options: --silent --skipFilter --snapshotSerializers - --testEnvironment + --testEnvironment, --env --testEnvironmentOptions --testFailureExitCode --testLocationInResults --testMatch --testPathIgnorePatterns - --testPathPattern + --testPathPatterns --testRegex --testResultsProcessor --testRunner @@ -418,6 +416,7 @@ Options: --useStderr --verbose --version + --waitForUnhandledRejections --watch --watchAll --watchPathIgnorePatterns diff --git a/packages/cli/config/getJestEnvironment.js b/packages/cli/config/getJestEnvironment.js new file mode 100644 index 0000000000..aac2f79021 --- /dev/null +++ b/packages/cli/config/getJestEnvironment.js @@ -0,0 +1,49 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +function getJestMajorVersion() { + const jestVersion = require('jest/package.json').version; + const majorVersion = parseInt(jestVersion.split('.')[0], 10); + return majorVersion; +} + +function getJestEnvironment() { + const majorVersion = getJestMajorVersion(); + + if (majorVersion >= 30) { + try { + require.resolve('@jest/environment-jsdom-abstract'); + require.resolve('jsdom'); + } catch { + throw new Error( + 'Jest 30+ requires @jest/environment-jsdom-abstract and jsdom. ' + + 'Please install them as dev dependencies.', + ); + } + return require.resolve('./jest-environment-jsdom'); + } + try { + require.resolve('jest-environment-jsdom'); + } catch { + throw new Error( + 'Jest 29 requires jest-environment-jsdom. ' + + 'Please install it as a dev dependency.', + ); + } + return require.resolve('jest-environment-jsdom'); +} + +module.exports = { getJestMajorVersion, getJestEnvironment }; diff --git a/packages/cli/config/jest-environment-jsdom/index.js b/packages/cli/config/jest-environment-jsdom/index.js new file mode 100644 index 0000000000..9d8b76eaf5 --- /dev/null +++ b/packages/cli/config/jest-environment-jsdom/index.js @@ -0,0 +1,61 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const JSDOMEnvironment = require('@jest/environment-jsdom-abstract').default; +const jsdom = require('jsdom'); + +/** + * A custom JSDOM environment that extends the abstract base and applies + * fixes for Web API globals that are missing or incorrectly implemented + * in JSDOM. + * + * Based on https://github.com/mswjs/jest-fixed-jsdom + */ +class FixedJSDOMEnvironment extends JSDOMEnvironment { + constructor(config, context) { + super(config, context, jsdom); + + // Fix Web API globals that JSDOM doesn't properly expose + this.global.TextDecoder = TextDecoder; + this.global.TextEncoder = TextEncoder; + this.global.TextDecoderStream = TextDecoderStream; + this.global.TextEncoderStream = TextEncoderStream; + this.global.ReadableStream = ReadableStream; + + this.global.Blob = Blob; + this.global.Headers = Headers; + this.global.FormData = FormData; + this.global.Request = Request; + this.global.Response = Response; + this.global.fetch = fetch; + this.global.AbortController = AbortController; + this.global.AbortSignal = AbortSignal; + this.global.structuredClone = structuredClone; + this.global.URL = URL; + this.global.URLSearchParams = URLSearchParams; + + this.global.BroadcastChannel = BroadcastChannel; + this.global.TransformStream = TransformStream; + this.global.WritableStream = WritableStream; + + // Needed to ensure `e instanceof Error` works as expected with errors thrown from + // any of the native APIs above. Without this, the JSDOM `Error` is what the test + // code will use for comparison with `e`, which fails the instanceof check. + this.global.Error = Error; + } +} + +module.exports = FixedJSDOMEnvironment; diff --git a/packages/cli/config/jest.js b/packages/cli/config/jest.js index f011d14b19..94e5b9c4a8 100644 --- a/packages/cli/config/jest.js +++ b/packages/cli/config/jest.js @@ -20,6 +20,10 @@ const crypto = require('crypto'); const glob = require('util').promisify(require('glob')); const { version } = require('../package.json'); const paths = require('@backstage/cli-common').findPaths(process.cwd()); +const { + getJestEnvironment, + getJestMajorVersion, +} = require('./getJestEnvironment'); const SRC_EXTS = ['ts', 'js', 'tsx', 'jsx', 'mts', 'cts', 'mjs', 'cjs']; @@ -211,7 +215,7 @@ function getRoleConfig(role, pkgJson) { }; if (FRONTEND_ROLES.includes(role)) { return { - testEnvironment: require.resolve('jest-environment-jsdom'), + testEnvironment: getJestEnvironment(), // The caching module loader is only used to speed up frontend tests, // as it breaks real dynamic imports of ESM modules. runtime: envOptions.oldTests @@ -279,7 +283,10 @@ async function getProjectConfig(targetPath, extraConfig, extraOptions) { ); } - if (options.testEnvironment === require.resolve('jest-environment-jsdom')) { + if ( + options.testEnvironment === getJestEnvironment() && + getJestMajorVersion() < 30 // Only needed when not running the custom env for Jest 30+ + ) { // FIXME https://github.com/jsdom/jsdom/issues/1724 options.setupFilesAfterEnv.unshift(require.resolve('cross-fetch/polyfill')); } diff --git a/packages/cli/config/jestCachingModuleLoader.js b/packages/cli/config/jestCachingModuleLoader.js index a3fdf106e9..5652ff67e7 100644 --- a/packages/cli/config/jestCachingModuleLoader.js +++ b/packages/cli/config/jestCachingModuleLoader.js @@ -14,6 +14,8 @@ * limitations under the License. */ +// 'jest-runtime' is included with jest and should be kept in sync with the installed jest version +// eslint-disable-next-line @backstage/no-undeclared-imports const { default: JestRuntime } = require('jest-runtime'); module.exports = class CachingJestRuntime extends JestRuntime { diff --git a/packages/cli/config/jestRejectNetworkRequests.js b/packages/cli/config/jestRejectNetworkRequests.js index de6b486809..ef577b3a22 100644 --- a/packages/cli/config/jestRejectNetworkRequests.js +++ b/packages/cli/config/jestRejectNetworkRequests.js @@ -22,7 +22,7 @@ const errorMessage = 'Network requests are not allowed in tests'; const origHttpAgent = http.globalAgent; const origHttpsAgent = https.globalAgent; const origFetch = global.fetch; -const origXMLHttpRequest = global.fetch; +const origXMLHttpRequest = global.XMLHttpRequest; http.globalAgent = new http.Agent({ lookup() { @@ -36,10 +36,21 @@ https.globalAgent = new https.Agent({ }, }); +const BLOCKING_FETCH_SYMBOL = Symbol.for( + 'backstage.jestRejectNetworkRequests.blockingFetch', +); + if (global.fetch) { - global.fetch = async () => { - throw new Error(errorMessage); + const blockingFetch = async (input, init) => { + // If global.fetch still has our marker, block the request + if (global.fetch[BLOCKING_FETCH_SYMBOL]) { + throw new Error(errorMessage); + } + // MSW (or something else) wrapped us - pass through + return origFetch(input, init); }; + blockingFetch[BLOCKING_FETCH_SYMBOL] = true; + global.fetch = blockingFetch; } if (global.XMLHttpRequest) { diff --git a/packages/cli/config/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..40a572da4d 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, diff --git a/packages/cli/package.json b/packages/cli/package.json index e6509c8736..23333cdbfd 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/cli", - "version": "0.34.5", + "version": "0.35.0", "description": "CLI for developing Backstage plugins and apps", "backstage": { "role": "cli" @@ -70,10 +70,9 @@ "@spotify/eslint-config-base": "^15.0.0", "@spotify/eslint-config-react": "^15.0.0", "@spotify/eslint-config-typescript": "^15.0.0", - "@swc/core": "^1.3.46", - "@swc/helpers": "^0.5.0", - "@swc/jest": "^0.2.22", - "@types/jest": "^29.5.11", + "@swc/core": "^1.15.6", + "@swc/helpers": "^0.5.17", + "@swc/jest": "^0.2.39", "@types/webpack-env": "^1.15.2", "@typescript-eslint/eslint-plugin": "^8.17.0", "@typescript-eslint/parser": "^8.16.0", @@ -88,7 +87,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 +99,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", @@ -109,11 +108,7 @@ "handlebars": "^4.7.3", "html-webpack-plugin": "^5.6.3", "inquirer": "^8.2.0", - "jest": "^29.7.0", - "jest-cli": "^29.7.0", "jest-css-modules": "^2.1.0", - "jest-environment-jsdom": "^29.0.2", - "jest-runtime": "^29.0.2", "json-schema": "^0.4.0", "lodash": "^4.17.21", "minimatch": "^9.0.0", @@ -167,14 +162,16 @@ "@backstage/plugin-scaffolder-node-test-utils": "workspace:^", "@backstage/test-utils": "workspace:^", "@backstage/theme": "workspace:^", - "@pmmmwh/react-refresh-webpack-plugin": "^0.5.7", + "@jest/environment-jsdom-abstract": "^30.0.0", + "@pmmmwh/react-refresh-webpack-plugin": "^0.6.0", "@types/cross-spawn": "^6.0.2", "@types/ejs": "^3.1.3", "@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/jest": "^30.0.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", @@ -188,25 +185,34 @@ "esbuild-loader": "^4.0.0", "eslint-webpack-plugin": "^4.2.0", "fork-ts-checker-webpack-plugin": "^9.0.0", + "jest": "^30.2.0", + "jsdom": "^27.1.0", "mini-css-extract-plugin": "^2.4.2", "msw": "^1.0.0", "nodemon": "^3.0.1", "terser-webpack-plugin": "^5.1.3", - "webpack": "~5.96.0", + "webpack": "~5.103.0", "webpack-dev-server": "^5.0.0" }, "peerDependencies": { + "@jest/environment-jsdom-abstract": "^30.0.0", "@module-federation/enhanced": "^0.9.0", - "@pmmmwh/react-refresh-webpack-plugin": "^0.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", + "jest": "^29.0.0 || ^30.0.0", + "jest-environment-jsdom": "*", + "jsdom": "^27.1.0", "mini-css-extract-plugin": "^2.4.2", "terser-webpack-plugin": "^5.1.3", - "webpack": "~5.96.0", + "webpack": "~5.103.0", "webpack-dev-server": "^5.0.0" }, "peerDependenciesMeta": { + "@jest/environment-jsdom-abstract": { + "optional": true + }, "@module-federation/enhanced": { "optional": true }, @@ -222,6 +228,12 @@ "fork-ts-checker-webpack-plugin": { "optional": true }, + "jest-environment-jsdom": { + "optional": true + }, + "jsdom": { + "optional": true + }, "mini-css-extract-plugin": { "optional": true }, diff --git a/packages/cli/src/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/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/build/lib/runner/runBackend.test.ts b/packages/cli/src/modules/build/lib/runner/runBackend.test.ts new file mode 100644 index 0000000000..2b97ae352c --- /dev/null +++ b/packages/cli/src/modules/build/lib/runner/runBackend.test.ts @@ -0,0 +1,178 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { runBackend } from './runBackend'; +import spawn from 'cross-spawn'; + +// Mock external dependencies +jest.mock('chokidar', () => ({ + watch: jest.fn(() => ({ + on: jest.fn().mockReturnThis(), + add: jest.fn(), + })), +})); + +jest.mock('cross-spawn', () => + jest.fn(() => ({ + on: jest.fn().mockReturnThis(), + once: jest.fn().mockReturnThis(), + kill: jest.fn(), + killed: false, + exitCode: null, + pid: 12345, + })), +); + +jest.mock('../ipc', () => ({ + IpcServer: jest.fn().mockImplementation(() => ({ + addChild: jest.fn(), + })), + ServerDataStore: { + bind: jest.fn(), + }, +})); + +jest.mock('ctrlc-windows', () => ({ + ctrlc: jest.fn(), +})); + +describe('runBackend', () => { + let originalEnv: NodeJS.ProcessEnv; + let originalPlatform: string; + const mockSpawn = spawn as jest.MockedFunction; + + beforeEach(() => { + // Use fake timers to control debounce + jest.useFakeTimers(); + + // Save original environment + originalEnv = { ...process.env }; + process.env = { NODE_ENV: 'test' }; + originalPlatform = process.platform; + + // Mock process.stdin.on to prevent actual stdin reading + jest.spyOn(process.stdin, 'on').mockReturnValue(process.stdin); + + // Mock process.once to prevent actual signal handling + jest.spyOn(process, 'once').mockReturnValue(process); + }); + + afterEach(() => { + // Restore original environment + process.env = originalEnv; + Object.defineProperty(process, 'platform', { + value: originalPlatform, + }); + + jest.clearAllMocks(); + jest.useRealTimers(); + }); + + describe('--no-node-snapshot argument handling', () => { + it('should pass --no-node-snapshot when NODE_OPTIONS is not set', () => { + delete process.env.NODE_OPTIONS; + + runBackend({ + entry: 'src/index', + }); + + // Fast-forward past the debounce delay (100ms) + jest.advanceTimersByTime(100); + + expect(mockSpawn).toHaveBeenCalled(); + const spawnArgs = mockSpawn.mock.calls[0][1] as string[]; + expect(spawnArgs).toContain('--no-node-snapshot'); + }); + + it('should pass --no-node-snapshot when NODE_OPTIONS exists without --node-snapshot', () => { + process.env.NODE_OPTIONS = '--max-old-space-size=4096'; + + runBackend({ + entry: 'src/index', + }); + + // Fast-forward past the debounce delay (100ms) + jest.advanceTimersByTime(100); + + expect(mockSpawn).toHaveBeenCalled(); + const spawnArgs = mockSpawn.mock.calls[0][1] as string[]; + expect(spawnArgs).toContain('--no-node-snapshot'); + }); + + it('should not pass --no-node-snapshot when --node-snapshot already exists in NODE_OPTIONS', () => { + process.env.NODE_OPTIONS = '--node-snapshot --max-old-space-size=4096'; + + runBackend({ + entry: 'src/index', + }); + + // Fast-forward past the debounce delay (100ms) + jest.advanceTimersByTime(100); + + expect(mockSpawn).toHaveBeenCalled(); + const spawnArgs = mockSpawn.mock.calls[0][1] as string[]; + expect(spawnArgs).not.toContain('--no-node-snapshot'); + }); + + it('should not pass --no-node-snapshot when --node-snapshot exists in the middle of NODE_OPTIONS', () => { + process.env.NODE_OPTIONS = + '--max-old-space-size=4096 --node-snapshot --inspect'; + + runBackend({ + entry: 'src/index', + }); + + // Fast-forward past the debounce delay (100ms) + jest.advanceTimersByTime(100); + + expect(mockSpawn).toHaveBeenCalled(); + const spawnArgs = mockSpawn.mock.calls[0][1] as string[]; + expect(spawnArgs).not.toContain('--no-node-snapshot'); + }); + + it('should pass --no-node-snapshot even with trailing spaces in NODE_OPTIONS', () => { + process.env.NODE_OPTIONS = '--max-old-space-size=4096 '; + + runBackend({ + entry: 'src/index', + }); + + // Fast-forward past the debounce delay (100ms) + jest.advanceTimersByTime(100); + + expect(mockSpawn).toHaveBeenCalled(); + const spawnArgs = mockSpawn.mock.calls[0][1] as string[]; + expect(spawnArgs).toContain('--no-node-snapshot'); + }); + + it('should pass --no-node-snapshot alongside other option args like --inspect', () => { + delete process.env.NODE_OPTIONS; + + runBackend({ + entry: 'src/index', + inspectEnabled: true, + }); + + // Fast-forward past the debounce delay (100ms) + jest.advanceTimersByTime(100); + + expect(mockSpawn).toHaveBeenCalled(); + const spawnArgs = mockSpawn.mock.calls[0][1] as string[]; + expect(spawnArgs).toContain('--no-node-snapshot'); + expect(spawnArgs).toContain('--inspect'); + }); + }); +}); diff --git a/packages/cli/src/modules/build/lib/runner/runBackend.ts b/packages/cli/src/modules/build/lib/runner/runBackend.ts index 49dccf804f..0550ff2563 100644 --- a/packages/cli/src/modules/build/lib/runner/runBackend.ts +++ b/packages/cli/src/modules/build/lib/runner/runBackend.ts @@ -47,7 +47,7 @@ export type RunBackendOptions = { }; export async function runBackend(options: RunBackendOptions) { - const envEnv = process.env as { NODE_ENV: string }; + const envEnv = process.env as { NODE_ENV: string; NODE_OPTIONS?: string }; if (!envEnv.NODE_ENV) { envEnv.NODE_ENV = 'development'; } @@ -115,6 +115,12 @@ export async function runBackend(options: RunBackendOptions) { } } + // Unless the user explicitly toggles node-snapshot, default to provide --no-node-snapshot to reduce number of steps to run scaffolder + // on Node LTS. + if (!envEnv.NODE_OPTIONS?.includes('--node-snapshot')) { + optionArgs.push('--no-node-snapshot'); + } + const userArgs = process.argv .slice(['node', 'backstage-cli', 'package', 'start'].length) .filter(arg => !optionArgs.includes(arg)); diff --git a/packages/cli/src/modules/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/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/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..992697d19c 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'); @@ -78,10 +78,36 @@ export default async (_opts: OptionValues, cmd: Command) => { process.env.TZ = 'UTC'; } + // Unless the user explicitly toggles node-snapshot, default to provide --no-node-snapshot to reduce number of steps to run scaffolder + // on Node LTS. + if (!process.env.NODE_OPTIONS?.includes('--node-snapshot')) { + process.env.NODE_OPTIONS = `${ + process.env.NODE_OPTIONS ? `${process.env.NODE_OPTIONS} ` : '' + }--no-node-snapshot`; + } + // This ensures that the process doesn't exit too early before stdout is flushed if (args.includes('--help')) { (process.stdout as any)._handle.setBlocking(true); } + // Because of the ongoing migration to v30 of jest, jest is no longer hard-depended to allow + // opt-in migration. Users instead need to add jest as a devDependency themselves and specify + // the version they want. This prints a helpful error message if jest is not found, i.e. they + // forgot/didn't know they had to add jest themselves. + try { + require.resolve('jest'); + } catch { + console.error( + [ + 'No Jest installation found in this project.', + '', + 'To support opt-in migration to Jest v30, the Backstage CLI now expects Jest to be installed as a devDependency.', + 'See the migration guide in the changelog for version options and their consequences.', + ].join('\n'), + ); + process.exit(1); + } + await require('jest').run(args); }; diff --git a/packages/cli/src/modules/test/commands/repo/test.ts b/packages/cli/src/modules/test/commands/repo/test.ts index f0520d29e2..2288d13476 100644 --- a/packages/cli/src/modules/test/commands/repo/test.ts +++ b/packages/cli/src/modules/test/commands/repo/test.ts @@ -17,12 +17,14 @@ import os from 'os'; import crypto from 'node:crypto'; import yargs from 'yargs'; +// 'jest-cli' is included with jest and should be kept in sync with the installed jest version +// eslint-disable-next-line @backstage/no-undeclared-imports import { run as runJest, yargsOptions as jestYargsOptions } from 'jest-cli'; import { relative as relativePath } from 'path'; import { Command, OptionValues } from 'commander'; 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 +65,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 +177,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; @@ -289,6 +291,14 @@ export async function command(opts: OptionValues, cmd: Command): Promise { process.env.TZ = 'UTC'; } + // Unless the user explicitly toggles node-snapshot, default to provide --no-node-snapshot to reduce number of steps to run scaffolder + // on Node LTS. + if (!process.env.NODE_OPTIONS?.includes('--node-snapshot')) { + process.env.NODE_OPTIONS = `${ + process.env.NODE_OPTIONS ? `${process.env.NODE_OPTIONS} ` : '' + }--no-node-snapshot`; + } + // This ensures that the process doesn't exit too early before stdout is flushed if (args.includes('--jest-help')) { removeOptionArg(args, '--jest-help'); diff --git a/packages/cli/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/cli/templates/frontend-plugin/package.json.hbs b/packages/cli/templates/frontend-plugin/package.json.hbs index 1ee8231228..9499d11b18 100644 --- a/packages/cli/templates/frontend-plugin/package.json.hbs +++ b/packages/cli/templates/frontend-plugin/package.json.hbs @@ -31,7 +31,9 @@ "react-use": "{{versionQuery 'react-use' '17.2.4'}}" }, "peerDependencies": { - "react": "{{versionQuery 'react' '^16.13.1 || ^17.0.0 || ^18.0.0'}}" + "react": "{{versionQuery 'react' '^16.13.1 || ^17.0.0 || ^18.0.0'}}", + "react-dom": "{{versionQuery 'react-dom' '^16.13.1 || ^17.0.0 || ^18.0.0'}}", + "react-router-dom": "{{versionQuery 'react-router-dom' '^6.0.0'}}" }, "devDependencies": { "@backstage/cli": "{{versionQuery '@backstage/cli'}}", @@ -42,7 +44,9 @@ "@testing-library/react": "{{versionQuery '@testing-library/react' '14.0.0'}}", "@testing-library/user-event": "{{versionQuery '@testing-library/user-event' '14.0.0'}}", "msw": "{{versionQuery 'msw' '1.0.0'}}", - "react": "{{versionQuery 'react' '^16.13.1 || ^17.0.0 || ^18.0.0'}}" + "react": "{{versionQuery 'react' '^16.13.1 || ^17.0.0 || ^18.0.0'}}", + "react-dom": "{{versionQuery 'react-dom' '^16.13.1 || ^17.0.0 || ^18.0.0'}}", + "react-router-dom": "{{versionQuery 'react-router-dom' '^6.0.0'}}" }, "files": [ "dist" diff --git a/packages/codemods/CHANGELOG.md b/packages/codemods/CHANGELOG.md index 89c2f0ecfa..22dd2a5df7 100644 --- a/packages/codemods/CHANGELOG.md +++ b/packages/codemods/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/codemods +## 0.1.53 + +### Patch Changes + +- 688f070: Updated to use new utilities from `@backstage/cli-common`. +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/cli-common@0.1.16 + +## 0.1.53-next.2 + +### Patch Changes + +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/cli-common@0.1.16-next.2 + +## 0.1.53-next.1 + +### Patch Changes + +- 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..01f5ae9168 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", "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 b48612c854..84ad0e04a1 100644 --- a/packages/config-loader/CHANGELOG.md +++ b/packages/config-loader/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/config-loader +## 1.10.7 + +### Patch Changes + +- 741c47a: Updated dependency `typescript-json-schema` to `^0.67.0`. +- Updated dependencies + - @backstage/cli-common@0.1.16 + +## 1.10.7-next.1 + +### Patch Changes + +- 741c47a: Updated dependency `typescript-json-schema` to `^0.67.0`. +- Updated dependencies + - @backstage/cli-common@0.1.16-next.2 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 1.10.7-next.0 + +### Patch Changes + +- 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 diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 15118bfb06..e48723e5f7 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/config-loader", - "version": "1.10.6", + "version": "1.10.7", "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": { @@ -57,7 +57,7 @@ "@backstage/cli": "workspace:^", "@types/json-schema-merge-allof": "^0.6.0", "@types/minimist": "^1.2.5", - "msw": "^1.0.0", + "msw": "^2.0.0", "zen-observable": "^0.10.0" } } diff --git a/packages/config-loader/src/loader.test.ts b/packages/config-loader/src/loader.test.ts index 8908b4268b..ea226aa557 100644 --- a/packages/config-loader/src/loader.test.ts +++ b/packages/config-loader/src/loader.test.ts @@ -17,7 +17,7 @@ import { AppConfig } from '@backstage/config'; import { loadConfig } from './loader'; import fs from 'fs-extra'; -import { rest } from 'msw'; +import { http, HttpResponse } from 'msw'; import { setupServer } from 'msw/node'; import { createMockDirectory } from '@backstage/backend-test-utils'; import { createDeferred } from '@backstage/types'; @@ -74,32 +74,28 @@ describe('loadConfig', () => { }); const server = setupServer(); - const initialLoaderHandler = rest.get( + const initialLoaderHandler = http.get( `https://some.domain.io/app-config.yaml`, - (_req, res, ctx) => { - return res( - ctx.body( - `app: + () => { + return new HttpResponse( + `app: title: Remote Example App sessionKey: 'abc123' escaped: \$\${Escaped} `, - ), ); }, ); - const reloadHandler = rest.get( + const reloadHandler = http.get( `https://some.domain.io/app-config.yaml`, - (_req, res, ctx) => { - return res( - ctx.body( - `app: + () => { + return new HttpResponse( + `app: title: NEW ReMOTe ExaMPLe App sessionKey: 'abc123' escaped: \$\${Escaped} `, - ), ); }, ); diff --git a/packages/config-loader/src/schema/collect.test.ts b/packages/config-loader/src/schema/collect.test.ts index d48c98fbe4..a732d982d3 100644 --- a/packages/config-loader/src/schema/collect.test.ts +++ b/packages/config-loader/src/schema/collect.test.ts @@ -15,14 +15,9 @@ */ import { createMockDirectory } from '@backstage/backend-test-utils'; -import { collectConfigSchemas } from './collect'; +import { collectConfigSchemas, internal } from './collect'; import path from 'path'; - -// cwd must be restored -const origDir = process.cwd(); -afterAll(() => { - process.chdir(origDir); -}); +import { execSync } from 'child_process'; const mockSchema = { type: 'object', @@ -37,6 +32,36 @@ const mockSchema = { describe('collectConfigSchemas', () => { const mockDir = createMockDirectory(); + // Jest 30's module resolver doesn't find nested node_modules in mock directories. + // Use a child process for native Node.js resolution instead. + const originalResolvePackagePath = internal.resolvePackagePath; + beforeAll(() => { + internal.resolvePackagePath = (name, options) => { + const basePath = (options && options.paths?.[0]) ?? process.cwd(); + const baseDir = basePath.endsWith('.json') + ? path.dirname(basePath) + : basePath; + + try { + return execSync('node', { + input: `console.log(require.resolve(${JSON.stringify( + name, + )}, { paths: [${JSON.stringify(baseDir)}] }))`, + encoding: 'utf8', + stdio: ['pipe', 'pipe', 'pipe'], + }).trim(); + } catch { + const error = new Error(`Cannot find module '${name}'`); + (error as NodeJS.ErrnoException).code = 'MODULE_NOT_FOUND'; + throw error; + } + }; + }); + + afterAll(() => { + internal.resolvePackagePath = originalResolvePackagePath; + }); + afterEach(() => { mockDir.clear(); }); diff --git a/packages/config-loader/src/schema/collect.ts b/packages/config-loader/src/schema/collect.ts index 16bd654946..5f4e54387d 100644 --- a/packages/config-loader/src/schema/collect.ts +++ b/packages/config-loader/src/schema/collect.ts @@ -37,6 +37,17 @@ const req = ? require : __non_webpack_require__; +/** + * Exported for test mocking. Jest 30's module resolver has issues with + * nested node_modules, requiring tests to use an alternative resolution strategy. + * @internal + */ +export const internal = { + resolvePackagePath(name: string, options?: { paths: string[] }): string { + return req.resolve(name, options); + }, +}; + /** * This collects all known config schemas across all dependencies of the app. */ @@ -62,11 +73,9 @@ export async function collectConfigSchemas( const { name, parentPath } = item; try { - pkgPath = req.resolve( + pkgPath = internal.resolvePackagePath( `${name}/package.json`, - parentPath && { - paths: [parentPath], - }, + parentPath ? { paths: [parentPath] } : undefined, ); } catch { // We can somewhat safely ignore packages that don't export package.json, diff --git a/packages/config-loader/src/sources/RemoteConfigSource.test.ts b/packages/config-loader/src/sources/RemoteConfigSource.test.ts index 372dc91823..acb5870c7d 100644 --- a/packages/config-loader/src/sources/RemoteConfigSource.test.ts +++ b/packages/config-loader/src/sources/RemoteConfigSource.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { rest } from 'msw'; +import { http, HttpResponse } from 'msw'; import { registerMswTestHooks } from '@backstage/backend-test-utils'; import { setupServer } from 'msw/node'; import { RemoteConfigSource } from './RemoteConfigSource'; @@ -26,15 +26,15 @@ describe('RemoteConfigSource', () => { it('should load config from a remote URL', async () => { worker.use( - rest.get('http://localhost/config.yaml', (_req, res, ctx) => - res( - ctx.body(` + http.get( + 'http://localhost/config.yaml', + () => + new HttpResponse(` app: title: Example App substituted: \${VALUE} escaped: \$\${VALUE} `), - ), ), ); @@ -61,9 +61,10 @@ app: it('should load and parse config from a remote URL', async () => { worker.use( - rest.get('http://localhost/config.json', (_req, res, ctx) => - res( - ctx.body( + http.get( + 'http://localhost/config.json', + () => + new HttpResponse( JSON.stringify({ app: { title: 'Example App', @@ -72,7 +73,6 @@ app: }, }), ), - ), ), ); @@ -102,12 +102,12 @@ app: let fetched = false; worker.use( - rest.get('http://localhost/config.yaml', (_req, res, ctx) => { + http.get('http://localhost/config.yaml', () => { if (!fetched) { fetched = true; - return res(ctx.body('x: 1')); + return new HttpResponse('x: 1'); } - return res(ctx.body('x: 2')); + return new HttpResponse('x: 2'); }), ); diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md index 34ba7e2146..2b2dbeccfd 100644 --- a/packages/core-app-api/CHANGELOG.md +++ b/packages/core-app-api/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/core-app-api +## 1.19.3 + +### Patch Changes + +- 75683ed: Added replay functionality to `AlertApiForwarder` to buffer and replay recent alerts to new subscribers, preventing missed alerts that were posted before subscription. +- 97cd16f: Internal update of translation imports. +- Updated dependencies + - @backstage/core-plugin-api@1.12.1 + +## 1.19.3-next.1 + +### Patch Changes + +- 75683ed: Added replay functionality to `AlertApiForwarder` to buffer and replay recent alerts to new subscribers, preventing missed alerts that were posted before subscription. +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 1.19.3-next.0 + +### Patch Changes + +- 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 diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index 617ef4258f..cd56c90ce8 100644 --- a/packages/core-app-api/package.json +++ b/packages/core-app-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/core-app-api", - "version": "1.19.2", + "version": "1.19.3", "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/FetchApi/PluginProtocolResolverFetchMiddleware.test.ts b/packages/core-app-api/src/apis/implementations/FetchApi/PluginProtocolResolverFetchMiddleware.test.ts index bd82d58736..2b2545f52f 100644 --- a/packages/core-app-api/src/apis/implementations/FetchApi/PluginProtocolResolverFetchMiddleware.test.ts +++ b/packages/core-app-api/src/apis/implementations/FetchApi/PluginProtocolResolverFetchMiddleware.test.ts @@ -60,12 +60,6 @@ describe('PluginProtocolResolverFetchMiddleware', () => { 'https://real.com:8080/base', 'https://real.com:8080/base?c=d&e=f#g', ], - [ - 'plugin://username:password@x?c=d&e=f#g', - 'x', - 'https://real.com:8080/base', - 'https://username:password@real.com:8080/base?c=d&e=f#g', - ], [ 'plugin://x?c=d&e=f#g', 'x', @@ -106,14 +100,14 @@ describe('PluginProtocolResolverFetchMiddleware', () => { expect(inner.mock.calls[0][0]).toBe('https://elsewhere.com'); expect(inner.mock.calls[0][1].body).toBe('123'); - await outer( - new Request('plugin://a', { - method: 'POST', - body: '123', - }), - ); + const inputRequest = new Request('plugin://a', { + method: 'POST', + body: '123', + }); + + await outer(inputRequest); expect(inner.mock.calls[1][0]).toBe('https://elsewhere.com'); - expect(inner.mock.calls[1][1].body).toEqual(Buffer.from('123', 'utf8')); + expect(inner.mock.calls[1][1]).toBe(inputRequest); }); }); diff --git a/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.test.ts b/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.test.ts index 5993543c33..9d320c4033 100644 --- a/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.test.ts +++ b/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.test.ts @@ -83,12 +83,9 @@ describe('AppIdentityProxy', () => { it('should navigate to target URL on sign out', async () => { const proxy = new AppIdentityProxy(); proxy.setTarget(mockIdentityApi, { signOutTargetUrl: '/foo' }); - Object.defineProperty(window, 'location', { - writable: true, - value: {}, - }); + const navigateSpy = jest.spyOn(proxy as any, 'navigateToUrl'); await proxy.signOut(); - expect(window.location.href).toBe('/foo'); + expect(navigateSpy).toHaveBeenCalledWith('/foo'); }); }); diff --git a/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts b/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts index 941ec90d66..161092fe9f 100644 --- a/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts +++ b/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts @@ -133,7 +133,11 @@ export class AppIdentityProxy implements IdentityApi { await this.#cookieAuthSignOut?.(); - window.location.href = this.signOutTargetUrl; + this.navigateToUrl(this.signOutTargetUrl); + } + + private navigateToUrl(url: string): void { + window.location.href = url; } enableCookieAuth(ctx: { diff --git a/packages/core-app-api/src/apis/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/ApiProvider.test.tsx b/packages/core-app-api/src/apis/system/ApiProvider.test.tsx index 095fc88627..4273587c00 100644 --- a/packages/core-app-api/src/apis/system/ApiProvider.test.tsx +++ b/packages/core-app-api/src/apis/system/ApiProvider.test.tsx @@ -117,16 +117,12 @@ describe('ApiProvider', () => { }).toThrow(/^API context is not available/); }).error, ).toEqual([ - expect.objectContaining({ - detail: new Error('API context is not available'), - type: 'unhandled exception', - }), - expect.objectContaining({ - detail: new Error('API context is not available'), - type: 'unhandled exception', - }), - expect.stringMatching( - /^The above error occurred in the component/, + expect.stringContaining('Error: API context is not available'), + expect.objectContaining({ type: 'unhandled-exception' }), + expect.stringContaining('Error: API context is not available'), + expect.objectContaining({ type: 'unhandled-exception' }), + expect.stringContaining( + 'The above error occurred in the component', ), ]); @@ -137,16 +133,12 @@ describe('ApiProvider', () => { }).toThrow(/^API context is not available/); }).error, ).toEqual([ - expect.objectContaining({ - detail: new Error('API context is not available'), - type: 'unhandled exception', - }), - expect.objectContaining({ - detail: new Error('API context is not available'), - type: 'unhandled exception', - }), - expect.stringMatching( - /^The above error occurred in the component/, + expect.stringContaining('Error: API context is not available'), + expect.objectContaining({ type: 'unhandled-exception' }), + expect.stringContaining('Error: API context is not available'), + expect.objectContaining({ type: 'unhandled-exception' }), + expect.stringContaining( + 'The above error occurred in the component', ), ]); }); @@ -163,16 +155,16 @@ describe('ApiProvider', () => { }).toThrow('No implementation available for apiRef{x}'); }).error, ).toEqual([ - expect.objectContaining({ - detail: new Error('No implementation available for apiRef{x}'), - type: 'unhandled exception', - }), - expect.objectContaining({ - detail: new Error('No implementation available for apiRef{x}'), - type: 'unhandled exception', - }), - expect.stringMatching( - /^The above error occurred in the component/, + expect.stringContaining( + 'Error: No implementation available for apiRef{x}', + ), + expect.objectContaining({ type: 'unhandled-exception' }), + expect.stringContaining( + 'Error: No implementation available for apiRef{x}', + ), + expect.objectContaining({ type: 'unhandled-exception' }), + expect.stringContaining( + 'The above error occurred in the component', ), ]); @@ -187,16 +179,16 @@ describe('ApiProvider', () => { }).toThrow('No implementation available for apiRef{x}'); }).error, ).toEqual([ - expect.objectContaining({ - detail: new Error('No implementation available for apiRef{x}'), - type: 'unhandled exception', - }), - expect.objectContaining({ - detail: new Error('No implementation available for apiRef{x}'), - type: 'unhandled exception', - }), - expect.stringMatching( - /^The above error occurred in the component/, + expect.stringContaining( + 'Error: No implementation available for apiRef{x}', + ), + expect.objectContaining({ type: 'unhandled-exception' }), + expect.stringContaining( + 'Error: No implementation available for apiRef{x}', + ), + expect.objectContaining({ type: 'unhandled-exception' }), + expect.stringContaining( + 'The above error occurred in the component', ), ]); }); diff --git a/packages/core-app-api/src/app/AppManager.test.tsx b/packages/core-app-api/src/app/AppManager.test.tsx index 3981da56ad..4a146678bc 100644 --- a/packages/core-app-api/src/app/AppManager.test.tsx +++ b/packages/core-app-api/src/app/AppManager.test.tsx @@ -670,14 +670,10 @@ describe('Integration Test', () => { }); expect(errorLogs).toEqual([ - expect.objectContaining({ - detail: new Error(expectedMessage), - type: 'unhandled exception', - }), - expect.objectContaining({ - detail: new Error(expectedMessage), - type: 'unhandled exception', - }), + expect.stringContaining(`Error: ${expectedMessage}`), + expect.objectContaining({ type: 'unhandled-exception' }), + expect.stringContaining(`Error: ${expectedMessage}`), + expect.objectContaining({ type: 'unhandled-exception' }), expect.stringContaining( 'The above error occurred in the component:', ), @@ -714,14 +710,10 @@ describe('Integration Test', () => { ).rejects.toThrow(expectedMessage); }); expect(errorLogs).toEqual([ - expect.objectContaining({ - detail: new Error(expectedMessage), - type: 'unhandled exception', - }), - expect.objectContaining({ - detail: new Error(expectedMessage), - type: 'unhandled exception', - }), + expect.stringContaining(`Error: ${expectedMessage}`), + expect.objectContaining({ type: 'unhandled-exception' }), + expect.stringContaining(`Error: ${expectedMessage}`), + expect.objectContaining({ type: 'unhandled-exception' }), expect.stringContaining( 'The above error occurred in the component:', ), diff --git a/packages/core-compat-api/CHANGELOG.md b/packages/core-compat-api/CHANGELOG.md index a8d28496f6..791b2cf1dc 100644 --- a/packages/core-compat-api/CHANGELOG.md +++ b/packages/core-compat-api/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/core-compat-api +## 0.5.5 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + +## 0.5.5-next.0 + +### Patch Changes + +- 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 diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json index 012fb137b9..f8d065a611 100644 --- a/packages/core-compat-api/package.json +++ b/packages/core-compat-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/core-compat-api", - "version": "0.5.4", + "version": "0.5.5", "backstage": { "role": "web-library" }, diff --git a/packages/core-compat-api/src/convertLegacyApp.test.tsx b/packages/core-compat-api/src/convertLegacyApp.test.tsx index 1cb2b757e3..7094eb798c 100644 --- a/packages/core-compat-api/src/convertLegacyApp.test.tsx +++ b/packages/core-compat-api/src/convertLegacyApp.test.tsx @@ -262,13 +262,16 @@ describe('convertLegacyApp', () => { ], }); + // Increase timeout for async rendering of complex catalog entity pages + const findOptions = { timeout: 5000 }; + // Overview const renderOverviewTest = await renderTestApp({ features: [catalogOverride, ...converted], initialRouteEntries: ['/catalog/default/test/x'], }); await expect( - renderOverviewTest.findByText('overview content'), + renderOverviewTest.findByText('overview content', {}, findOptions), ).resolves.toBeInTheDocument(); renderOverviewTest.unmount(); @@ -277,7 +280,7 @@ describe('convertLegacyApp', () => { initialRouteEntries: ['/catalog/default/other/x'], }); await expect( - renderOverviewOther.findByText('other overview content'), + renderOverviewOther.findByText('other overview content', {}, findOptions), ).resolves.toBeInTheDocument(); renderOverviewOther.unmount(); @@ -287,7 +290,7 @@ describe('convertLegacyApp', () => { initialRouteEntries: ['/catalog/default/test/x/foo'], }); await expect( - renderFooTest.findByText('foo content'), + renderFooTest.findByText('foo content', {}, findOptions), ).resolves.toBeInTheDocument(); renderFooTest.unmount(); @@ -296,7 +299,7 @@ describe('convertLegacyApp', () => { initialRouteEntries: ['/catalog/default/other/x/foo'], }); await expect( - renderFooOther.findByText('other foo content'), + renderFooOther.findByText('other foo content', {}, findOptions), ).resolves.toBeInTheDocument(); renderFooOther.unmount(); @@ -306,7 +309,7 @@ describe('convertLegacyApp', () => { initialRouteEntries: ['/catalog/default/test/x/bar'], }); await expect( - renderBarTest.findByText('bar content'), + renderBarTest.findByText('bar content', {}, findOptions), ).resolves.toBeInTheDocument(); renderBarTest.unmount(); @@ -315,7 +318,7 @@ describe('convertLegacyApp', () => { initialRouteEntries: ['/catalog/default/other/x/bar'], }); await expect( - renderBarOther.findByText('other overview content'), + renderBarOther.findByText('other overview content', {}, findOptions), ).resolves.toBeInTheDocument(); // /bar does not exist, fall back to rendering overview renderBarOther.unmount(); }); diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md index 3238b8d627..7346287cf6 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,65 @@ # @backstage/core-components +## 0.18.4 + +### Patch Changes + +- 9a942a4: Fixed bug in the `LogViewer` component where shift + click always opened a new window instead of just changing the selection. + + In addition, improved the `LogViewer` component by a few usability enhancements: + + - Added support for multiple selections using cmd/ctrl + click + - Improved the generated hash that is added to the URL to also support ranges & multiple selections + - Added an hover effect & info tooltip to the "Copy to clipboard" button to indicate its functionality + - Added some color and a separator to the line numbers to improve readability + +- 207c3c8: long words like urls now breaks to new line on warning panels instead of overflowing the container +- 4c00303: Add `tooltipClasses` prop to `OverflowTooltip` component to allow customisation of the tooltip +- 5d52dab: Add i18n support for LogViewer search control +- f6b49ce: added support for wrapLongLines option in CodeSnippet +- Updated dependencies + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + +## 0.18.4-next.2 + +### Patch Changes + +- 4c00303: Add `tooltipClasses` prop to `OverflowTooltip` component to allow customisation of the tooltip +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/theme@0.7.1-next.0 + - @backstage/version-bridge@1.0.11 + +## 0.18.4-next.1 + +### Patch Changes + +- 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 diff --git a/packages/core-components/package.json b/packages/core-components/package.json index 30732b0b15..536f0ee9f0 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/core-components", - "version": "0.18.3", + "version": "0.18.4", "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 02fbb04805..5e842929e6 100644 --- a/packages/core-components/report.api.md +++ b/packages/core-components/report.api.md @@ -188,6 +188,7 @@ export interface CodeSnippetProps { showCopyCodeButton?: boolean; showLineNumbers?: boolean; text: string; + wrapLongLines?: boolean; } // Warning: (ae-forgotten-export) The symbol "Props_12" needs to be exported by the entry point index.d.ts diff --git a/packages/core-components/src/components/CodeSnippet/CodeSnippet.tsx b/packages/core-components/src/components/CodeSnippet/CodeSnippet.tsx index 8c31bc4e6d..e79dc07ad3 100644 --- a/packages/core-components/src/components/CodeSnippet/CodeSnippet.tsx +++ b/packages/core-components/src/components/CodeSnippet/CodeSnippet.tsx @@ -57,6 +57,14 @@ export interface CodeSnippetProps { * Array of line numbers to highlight */ highlightedNumbers?: number[]; + /** + * Whether to style the `` block with `white-space: pre-wrap` or `white-space: pre` + * + * @remarks + * + * Default: false (`white-space: pre`) + */ + wrapLongLines?: boolean; /** * Custom styles applied to code * @@ -79,6 +87,7 @@ export function CodeSnippet(props: CodeSnippetProps) { language, showLineNumbers = false, highlightedNumbers, + wrapLongLines, customStyle, showCopyCodeButton = false, } = props; @@ -94,6 +103,7 @@ export function CodeSnippet(props: CodeSnippetProps) { style={mode} showLineNumbers={showLineNumbers} wrapLines + wrapLongLines={wrapLongLines} lineNumberStyle={{ color: theme.palette.textVerySubtle }} lineProps={(lineNumber: number) => highlightedNumbers?.includes(lineNumber) diff --git a/packages/core-components/src/components/Link/Link.test.tsx b/packages/core-components/src/components/Link/Link.test.tsx index c132d832a2..c178ba6cbf 100644 --- a/packages/core-components/src/components/Link/Link.test.tsx +++ b/packages/core-components/src/components/Link/Link.test.tsx @@ -50,7 +50,7 @@ describe('', () => { External Link, ); const externalLink = screen.getByRole('link', { - name: 'External Link , Opens in a new window', + name: 'External Link, Opens in a new window', }); const externalLinkIcon = container.querySelector('svg'); expect(externalLink).not.toContainElement(externalLinkIcon); @@ -62,6 +62,7 @@ describe('', () => { External Link , ); + // Note: when externalLinkIcon is present, the SVG adds whitespace to the accessible name const externalLink = screen.getByRole('link', { name: 'External Link , Opens in a new window', }); diff --git a/packages/core-components/src/components/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 238361f9a9..83fb3a84ad 100644 --- a/packages/core-components/src/components/LogViewer/styles.ts +++ b/packages/core-components/src/components/LogViewer/styles.ts @@ -89,15 +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, - userSelect: 'none', + 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/TabbedLayout/TabbedLayout.test.tsx b/packages/core-components/src/components/TabbedLayout/TabbedLayout.test.tsx index 1be58d2dcf..3e4ab1df7c 100644 --- a/packages/core-components/src/components/TabbedLayout/TabbedLayout.test.tsx +++ b/packages/core-components/src/components/TabbedLayout/TabbedLayout.test.tsx @@ -47,18 +47,20 @@ describe('TabbedLayout', () => { }); expect(error).toEqual([ + expect.stringContaining( + 'Error: Child of TabbedLayout must be an TabbedLayout.Route', + ), expect.objectContaining({ - detail: new Error( - 'Child of TabbedLayout must be an TabbedLayout.Route', - ), + type: 'unhandled-exception', }), + expect.stringContaining( + 'Error: Child of TabbedLayout must be an TabbedLayout.Route', + ), expect.objectContaining({ - detail: new Error( - 'Child of TabbedLayout must be an TabbedLayout.Route', - ), + type: 'unhandled-exception', }), - expect.stringMatching( - /The above error occurred in the component/, + expect.stringContaining( + 'The above error occurred in the component', ), ]); }); diff --git a/packages/core-components/src/components/Table/Table.test.tsx b/packages/core-components/src/components/Table/Table.test.tsx index dfae66d1c2..dab5a462c2 100644 --- a/packages/core-components/src/components/Table/Table.test.tsx +++ b/packages/core-components/src/components/Table/Table.test.tsx @@ -68,7 +68,7 @@ describe('', () => {
, ); expect(rendered.getByText('second value, first row')).toHaveStyle({ - color: 'blue', + color: 'rgb(0, 0, 255)', // blue }); }); @@ -85,7 +85,7 @@ describe('
', () => {
, ); expect(rendered.getByText('second value, first row')).toHaveStyle({ - color: 'blue', + color: 'rgb(0, 0, 255)', // blue 'font-weight': 700, }); }); @@ -115,10 +115,10 @@ describe('
', () => {
, ); expect(rendered.getByText('second value, first row')).toHaveStyle({ - color: 'green', + color: 'rgb(0, 128, 0)', // green }); expect(rendered.getByText('second value, second row')).toHaveStyle({ - color: 'red', + color: 'rgb(255, 0, 0)', // red }); }); @@ -135,11 +135,11 @@ describe('
', () => {
, ); expect(rendered.getByText('second value, first row')).toHaveStyle({ - color: 'green', + color: 'rgb(0, 128, 0)', // green 'font-weight': 700, }); expect(rendered.getByText('second value, second row')).toHaveStyle({ - color: 'red', + color: 'rgb(255, 0, 0)', // red 'font-weight': 700, }); }); @@ -165,11 +165,11 @@ describe('
', () => { expect(rendered.getByText(column1.title).closest('th')).not.toHaveStyle( { - backgroundColor: 'pink', + backgroundColor: 'rgb(255, 192, 203)', // pink }, ); expect(rendered.getByText(column2.title).closest('th')).toHaveStyle({ - backgroundColor: 'pink', + backgroundColor: 'rgb(255, 192, 203)', // pink }); }); 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/ErrorBoundary/ErrorBoundary.test.tsx b/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.test.tsx index 4011fd2125..65e8a55801 100644 --- a/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.test.tsx +++ b/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.test.tsx @@ -67,18 +67,20 @@ describe('', () => { }); expect(error).toEqual([ + expect.stringContaining('Error: Bomb'), expect.objectContaining({ - detail: new Error('Bomb'), + type: 'unhandled-exception', }), + expect.stringContaining('Error: Bomb'), expect.objectContaining({ - detail: new Error('Bomb'), + type: 'unhandled-exception', }), - expect.stringMatching( - /^The above error occurred in the component:/, + expect.stringContaining( + 'The above error occurred in the component:', ), - expect.stringMatching(/^ErrorBoundary/), - expect.stringMatching(/Warning: findDOMNode/), // React warning, unfortunate but currently true + expect.stringContaining('ErrorBoundary'), + expect.stringContaining('Warning: findDOMNode'), // React warning, unfortunate but currently true ]); - expect(error.length).toEqual(5); + expect(error.length).toEqual(7); }); }); diff --git a/packages/core-components/src/layout/Sidebar/Items.test.tsx b/packages/core-components/src/layout/Sidebar/Items.test.tsx index 3121228ccd..35a0793409 100644 --- a/packages/core-components/src/layout/Sidebar/Items.test.tsx +++ b/packages/core-components/src/layout/Sidebar/Items.test.tsx @@ -96,7 +96,7 @@ describe('Items', () => { it('should render a button with custom style', async () => { expect( await screen.findByRole('button', { name: /create/i }), - ).toHaveStyle(`background-color: transparent`); + ).toHaveStyle(`background-color: rgba(0, 0, 0, 0)`); // transparent }); it('should send button clicks to analytics', async () => { 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 37e1b1f721..8c6c617bcb 100644 --- a/packages/core-plugin-api/CHANGELOG.md +++ b/packages/core-plugin-api/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/core-plugin-api +## 1.12.1 + +### Patch Changes + +- 358c6f7: The `useApp` and `useRouteRef` functions are now forwards compatible with the new frontend system. Along with the previous route reference changes this means that there is no longer a need to use `compatWrapper` from `@backstage/core-compat-api` to make code based on `@backstage/core-plugin-api` compatible with `@backstage/frontend-plugin-api` APIs. +- 97cd16f: Reversed the relationship between the old `@backstage/core-plugin-api` and the new `@backstage/frontend-plugin-api`. Previously, the a lot of API definitions and utilities where defined in the old and re-exported from the old, but this change flips that around so that they now reside in the new package and are re-exported from the old. The external API of both packages remain the same, but this is a step towards being able to add further compatibility with the new frontend system built into the old. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + +## 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 diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json index 877abb6114..ef4982e07e 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.12.0", + "version": "1.12.1", "description": "Core API used by Backstage plugins", "backstage": { "role": "web-library" @@ -51,6 +51,7 @@ "dependencies": { "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", + "@backstage/frontend-plugin-api": "workspace:^", "@backstage/types": "workspace:^", "@backstage/version-bridge": "workspace:^", "history": "^5.0.0", @@ -59,7 +60,6 @@ "devDependencies": { "@backstage/cli": "workspace:^", "@backstage/core-app-api": "workspace:^", - "@backstage/frontend-plugin-api": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", 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 1e31cc5eb8..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<{ @@ -433,60 +374,24 @@ export type 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 const FeatureFlagState: { - readonly None: 0; - readonly Active: 1; -}; +export { FeatureFlagState }; -// @public (undocumented) -export type FeatureFlagState = - (typeof FeatureFlagState)[keyof typeof FeatureFlagState]; +export { FetchApi }; -// @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; +export { fetchApiRef }; // @public export function getComponentData( @@ -494,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< @@ -553,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< @@ -655,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< @@ -679,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 = ( @@ -704,61 +519,20 @@ export type RouteRef = { readonly T: Params; }; -// @public -export type SessionApi = { - signIn(): Promise; - signOut(): Promise; - sessionState$(): Observable; -}; +export { SessionApi }; -// @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; -} +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 = { @@ -770,19 +544,14 @@ export type 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; @@ -809,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/AppLanguageApi.ts b/packages/core-plugin-api/src/apis/definitions/AppLanguageApi.ts deleted file mode 100644 index 1207e84755..0000000000 --- a/packages/core-plugin-api/src/apis/definitions/AppLanguageApi.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { 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 }>; -}; - -/** - * @alpha - */ -export const appLanguageApiRef: ApiRef = createApiRef({ - id: 'core.applanguage', -}); 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 d4429975cc..9b192e784d 100644 --- a/packages/core-plugin-api/src/apis/definitions/FeatureFlagsApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/FeatureFlagsApi.ts @@ -13,114 +13,11 @@ * 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'; - -/** - * 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', -}); +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 9a0ddcd958..a60c84c635 100644 --- a/packages/core-plugin-api/src/apis/definitions/auth.ts +++ b/packages/core-plugin-api/src/apis/definitions/auth.ts @@ -16,493 +16,28 @@ /* 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 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', -}); +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/extensions/extensions.test.tsx b/packages/core-plugin-api/src/extensions/extensions.test.tsx index 75bb4ae443..59fe0d118d 100644 --- a/packages/core-plugin-api/src/extensions/extensions.test.tsx +++ b/packages/core-plugin-api/src/extensions/extensions.test.tsx @@ -26,7 +26,6 @@ import { createReactExtension, createRoutableExtension, } from './extensions'; -import { ForwardedError } from '@backstage/errors'; jest.mock('../app'); @@ -117,7 +116,13 @@ describe('extensions', () => { render(); }); screen.getByText('Error in my-plugin'); - expect(errors[0]).toMatchObject({ detail: new Error('Test error') }); + expect(errors).toEqual([ + expect.stringContaining('Error: Test error'), + expect.objectContaining({ type: 'unhandled-exception' }), + expect.stringContaining('Error: Test error'), + expect.objectContaining({ type: 'unhandled-exception' }), + expect.stringContaining('The above error occurred in'), + ]); }); it('should handle failed lazy loads', async () => { @@ -154,12 +159,17 @@ describe('extensions', () => { screen.getByText( 'Error in my-plugin: Error: Failed lazy loading of the BrokenComponent extension, try to reload the page; caused by Error: Test error', ); - expect(errors[0]).toMatchObject({ - detail: new ForwardedError( - 'Failed lazy loading of the BrokenComponent extension, try to reload the page', - new Error('Test error'), + expect(errors).toEqual([ + expect.stringContaining( + 'Error: Failed lazy loading of the BrokenComponent extension, try to reload the page', ), - }); + expect.objectContaining({ type: 'unhandled-exception' }), + expect.stringContaining( + 'Error: Failed lazy loading of the BrokenComponent extension, try to reload the page', + ), + expect.objectContaining({ type: 'unhandled-exception' }), + expect.stringContaining('The above error occurred in'), + ]); }); it('should wrap extended component with analytics context', async () => { 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/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 7d745f36e5..92fff85f58 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/create-app +## 0.7.7 + +### Patch Changes + +- 336db00: Bumped create-app version. +- 2bae83a: Updated engines to support Node 22 or 24 +- c8c2329: Add proxy configuration from env-vars to create-app tasks +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/cli-common@0.1.16 + +## 0.7.7-next.2 + +### Patch Changes + +- 2bae83a: Updated engines to support Node 22 or 24 +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/cli-common@0.1.16-next.2 + +## 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 diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 22c03cba3d..aaf686f58d 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", + "version": "0.7.7", "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/templates/default-app/package.json.hbs b/packages/create-app/templates/default-app/package.json.hbs index 8399c6505a..c1e4a51140 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", @@ -32,6 +32,10 @@ "@backstage/cli": "^{{version '@backstage/cli'}}", "@backstage/e2e-test-utils": "^{{version '@backstage/e2e-test-utils'}}", "@playwright/test": "^1.32.3", + "@jest/environment-jsdom-abstract": "^30.0.0", + "@types/jest": "^30.0.0", + "jest": "^30.2.0", + "jsdom": "^27.1.0", "node-gyp": "^10.0.0", "prettier": "^2.3.2", "typescript": "~5.8.0" 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/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index b46415a8cb..8cee8bab77 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,43 @@ # @backstage/dev-utils +## 1.1.18 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/ui@0.10.0 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/app-defaults@1.7.3 + - @backstage/integration-react@1.2.13 + +## 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 diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index a2b5b89a85..e2c963e40f 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", + "version": "1.1.18", "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 6190aa342d..3931c55007 100644 --- a/packages/e2e-test/CHANGELOG.md +++ b/packages/e2e-test/CHANGELOG.md @@ -1,5 +1,22 @@ # e2e-test +## 0.2.35 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.7.7 + - @backstage/cli-common@0.1.16 + +## 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 diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json index c118650a41..efceeef449 100644 --- a/packages/e2e-test/package.json +++ b/packages/e2e-test/package.json @@ -1,6 +1,6 @@ { "name": "e2e-test", - "version": "0.2.34", + "version": "0.2.35", "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/errors/src/errors/common.test.ts b/packages/errors/src/errors/common.test.ts index dfa9743616..4c496614ab 100644 --- a/packages/errors/src/errors/common.test.ts +++ b/packages/errors/src/errors/common.test.ts @@ -23,7 +23,7 @@ describe('common', () => { const error = new E('abcdef'); expect(error.name).toBe(name); expect(error.message).toBe('abcdef'); - expect(error.stack).toContain(__filename); + expect(error.stack).toContain('common.test.ts'); expect(error.toString()).toContain(name); expect(error.toString()).toContain('abcdef'); } diff --git a/packages/frontend-app-api/CHANGELOG.md b/packages/frontend-app-api/CHANGELOG.md index 447899badb..625c96f42a 100644 --- a/packages/frontend-app-api/CHANGELOG.md +++ b/packages/frontend-app-api/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/frontend-app-api +## 0.13.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-plugin-api@1.12.1 + - @backstage/frontend-defaults@0.3.4 + +## 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 diff --git a/packages/frontend-app-api/package.json b/packages/frontend-app-api/package.json index ad5762a6f3..1c1f486e12 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", + "version": "0.13.3", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-defaults/CHANGELOG.md b/packages/frontend-defaults/CHANGELOG.md index 707062c0bb..eea14a159c 100644 --- a/packages/frontend-defaults/CHANGELOG.md +++ b/packages/frontend-defaults/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/frontend-defaults +## 0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.3.3 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/frontend-app-api@0.13.3 + +## 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 diff --git a/packages/frontend-defaults/package.json b/packages/frontend-defaults/package.json index 345bfcfb87..9cfacb3237 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", + "version": "0.3.4", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-dynamic-feature-loader/CHANGELOG.md b/packages/frontend-dynamic-feature-loader/CHANGELOG.md index 347af8fff9..58cb740bd5 100644 --- a/packages/frontend-dynamic-feature-loader/CHANGELOG.md +++ b/packages/frontend-dynamic-feature-loader/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/frontend-dynamic-feature-loader +## 0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + +## 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 diff --git a/packages/frontend-dynamic-feature-loader/package.json b/packages/frontend-dynamic-feature-loader/package.json index 4d15292803..c8771ca154 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", + "version": "0.1.8", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-dynamic-feature-loader/src/loader.test.tsx b/packages/frontend-dynamic-feature-loader/src/loader.test.tsx index b2f6486766..c0bf5822cc 100644 --- a/packages/frontend-dynamic-feature-loader/src/loader.test.tsx +++ b/packages/frontend-dynamic-feature-loader/src/loader.test.tsx @@ -655,7 +655,7 @@ describe('dynamicFrontendFeaturesLoader', () => { const errorCalls = mocks.console.error.mock.calls.flatMap(e => e[0]); expect(errorCalls).toEqual([ - `Failed fetching module federation configuration of dynamic frontend plugins: FetchError: invalid json response body at http://localhost:7007/.backstage/dynamic-features/remotes reason: Unexpected end of JSON input`, + `Failed fetching module federation configuration of dynamic frontend plugins: SyntaxError: Unexpected end of JSON input`, ]); const warnCalls = mocks.console.warn.mock.calls.flatMap(e => e[0]); expect(warnCalls).toEqual([]); diff --git a/packages/frontend-internal/CHANGELOG.md b/packages/frontend-internal/CHANGELOG.md index f914c9036b..de5b02e547 100644 --- a/packages/frontend-internal/CHANGELOG.md +++ b/packages/frontend-internal/CHANGELOG.md @@ -1,5 +1,21 @@ # @internal/frontend +## 0.0.16 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + +## 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 diff --git a/packages/frontend-internal/package.json b/packages/frontend-internal/package.json index 216cdbc0fa..705aec5835 100644 --- a/packages/frontend-internal/package.json +++ b/packages/frontend-internal/package.json @@ -1,6 +1,6 @@ { "name": "@internal/frontend", - "version": "0.0.15", + "version": "0.0.16", "backstage": { "role": "web-library", "inline": true @@ -25,8 +25,7 @@ "dependencies": { "@backstage/frontend-plugin-api": "workspace:^", "@backstage/types": "workspace:^", - "@backstage/version-bridge": "workspace:^", - "zod": "^3.22.4" + "@backstage/version-bridge": "workspace:^" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/packages/frontend-plugin-api/CHANGELOG.md b/packages/frontend-plugin-api/CHANGELOG.md index 25396cf06b..f2c4f2029c 100644 --- a/packages/frontend-plugin-api/CHANGELOG.md +++ b/packages/frontend-plugin-api/CHANGELOG.md @@ -1,5 +1,47 @@ # @backstage/frontend-plugin-api +## 0.13.2 + +### Patch Changes + +- 75683ed: Added a new `errorPresentation` prop to `ExtensionBoundary` to control how errors are presented to the user. The default is `'error-display'`, which is the current behavior of showing the error in the `ErrorDisplay` component. The new option is `'error-api'`, posts errors to the `ErrorApi` and does not allow retries. + + The `AppRootElementBlueprint` now wraps its element in an `ErrorBoundary` using the new `'error-api'` presentation mode. + +- 0bc1ce9: Fixed a versioning conflict that could result in a `.withContext` is not a function error. +- f3f84f1: Made the return type of `.withOverrides` to be simplified. +- 97cd16f: Reversed the relationship between the old `@backstage/core-plugin-api` and the new `@backstage/frontend-plugin-api`. Previously, the a lot of API definitions and utilities where defined in the old and re-exported from the old, but this change flips that around so that they now reside in the new package and are re-exported from the old. The external API of both packages remain the same, but this is a step towards being able to add further compatibility with the new frontend system built into the old. +- 9b8bde4: Removed unnecessary dependencies on `@backstage/core-components`, `@backstage/config`, `@material-ui/core`, and `lodash`. + +## 0.13.2-next.1 + +### Patch Changes + +- 75683ed: Added a new `errorPresentation` prop to `ExtensionBoundary` to control how errors are presented to the user. The default is `'error-display'`, which is the current behavior of showing the error in the `ErrorDisplay` component. The new option is `'error-api'`, posts errors to the `ErrorApi` and does not allow retries. + + The `AppRootElementBlueprint` now wraps its element in an `ErrorBoundary` using the new `'error-api'` presentation mode. + +- f3f84f1: Made the return type of `.withOverrides` to be simplified. +- Updated dependencies + - @backstage/core-components@0.18.4-next.2 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## 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 diff --git a/packages/frontend-plugin-api/package.json b/packages/frontend-plugin-api/package.json index d5ee6427ad..bda034ac78 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.13.0", + "version": "0.13.2", "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,17 +40,15 @@ "test": "backstage-cli package test" }, "dependencies": { - "@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", - "lodash": "^4.17.21", "zod": "^3.22.4", "zod-to-json-schema": "^3.21.4" }, "devDependencies": { "@backstage/cli": "workspace:^", + "@backstage/config": "workspace:^", "@backstage/frontend-app-api": "workspace:^", "@backstage/frontend-test-utils": "workspace:^", "@backstage/test-utils": "workspace:^", diff --git a/packages/frontend-plugin-api/report.api.md b/packages/frontend-plugin-api/report.api.md index 370e86727b..fbe7110d80 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 type { 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 { z } from 'zod'; +import { SwappableComponentRef as SwappableComponentRef_2 } from '@backstage/frontend-plugin-api'; +import type { 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 { @@ -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,23 +413,37 @@ 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< @@ -769,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 { @@ -805,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) @@ -869,7 +978,7 @@ export interface ExtensionBlueprint< // (undocumented) make< TName extends string | undefined, - TParamsInput extends AnyParamsInput>, + TParamsInput extends AnyParamsInput_2>, UParentInputs extends ExtensionDataRef, >(args: { name?: TName; @@ -897,10 +1006,10 @@ export interface ExtensionBlueprint< }, UFactoryOutput extends ExtensionDataValue, UNewOutput extends ExtensionDataRef, + UParentInputs extends ExtensionDataRef, TExtraInputs extends { [inputName in string]: ExtensionInput; - }, - UParentInputs extends ExtensionDataRef, + } = {}, >(args: { name?: TName; attachTo?: ExtensionDefinitionAttachTo & @@ -924,7 +1033,7 @@ export interface ExtensionBlueprint< }; factory( originalFactory: < - TParamsInput extends AnyParamsInput>, + TParamsInput extends AnyParamsInput_2>, >( params: TParamsInput extends ExtensionBlueprintDefineParams ? TParamsInput @@ -954,26 +1063,30 @@ export interface ExtensionBlueprint< UFactoryOutput >; }): OverridableExtensionDefinition<{ - 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] + 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']; @@ -1033,6 +1146,8 @@ export interface ExtensionBoundaryProps { // (undocumented) children: ReactNode; // (undocumented) + errorPresentation?: 'error-api' | 'error-display'; + // (undocumented) node: AppNode; } @@ -1201,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; @@ -1283,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; }, @@ -1306,7 +1474,7 @@ export const IconBundleBlueprint: ExtensionBlueprint<{ config: {}; configInput: {}; dataRefs: { - icons: ConfigurableExtensionDataRef< + icons: ConfigurableExtensionDataRef_2< { [x: string]: IconComponent; }, @@ -1330,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<{ @@ -1378,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', @@ -1398,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', @@ -1413,7 +1597,7 @@ export const NavItemBlueprint: ExtensionBlueprint<{ // @public (undocumented) export const NotFoundErrorPage: { (props: NotFoundErrorPageProps): JSX.Element | null; - ref: SwappableComponentRef; + ref: SwappableComponentRef_2; }; // @public (undocumented) @@ -1421,25 +1605,66 @@ 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< @@ -1460,7 +1685,7 @@ export interface OverridableExtensionDefinition< TExtraInputs extends { [inputName in string]: ExtensionInput; }, - TParamsInput extends AnyParamsInput_2>, + TParamsInput extends AnyParamsInput>, UParentInputs extends ExtensionDataRef, >( args: Expand< @@ -1486,7 +1711,7 @@ export interface OverridableExtensionDefinition< }; factory?( originalFactory: < - TFactoryParamsReturn extends AnyParamsInput_2< + TFactoryParamsReturn extends AnyParamsInput< NonNullable >, >( @@ -1583,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`]; @@ -1592,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; @@ -1611,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< @@ -1644,14 +1874,22 @@ 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) @@ -1686,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', {} @@ -1701,7 +1939,7 @@ export const RouterBlueprint: ExtensionBlueprint<{ config: {}; configInput: {}; dataRefs: { - component: ConfigurableExtensionDataRef< + component: ConfigurableExtensionDataRef_2< (props: { children: ReactNode }) => JSX.Element | null, 'app.router.wrapper', {} @@ -1734,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', {} @@ -1755,7 +2013,7 @@ export const SignInPageBlueprint: ExtensionBlueprint<{ config: {}; configInput: {}; dataRefs: { - component: ConfigurableExtensionDataRef< + component: ConfigurableExtensionDataRef_2< ComponentType, 'core.sign-in-page.component', {} @@ -1763,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< @@ -1782,7 +2067,7 @@ export interface SubRouteRef< } // @public -export const SwappableComponentBlueprint: ExtensionBlueprint<{ +export const SwappableComponentBlueprint: ExtensionBlueprint_2<{ kind: 'component'; params: >(params: { component: Ref extends SwappableComponentRef< @@ -1798,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 @@ -1813,7 +2098,7 @@ export const SwappableComponentBlueprint: ExtensionBlueprint<{ | (() => Promise<(props: IInnerComponentProps) => JSX.Element | null>) : never; }>; - output: ExtensionDataRef< + output: ExtensionDataRef_2< { ref: SwappableComponentRef; loader: @@ -1827,7 +2112,7 @@ export const SwappableComponentBlueprint: ExtensionBlueprint<{ config: {}; configInput: {}; dataRefs: { - component: ConfigurableExtensionDataRef< + component: ConfigurableExtensionDataRef_2< { ref: SwappableComponentRef; loader: @@ -1863,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, @@ -1902,7 +2208,7 @@ export const TranslationBlueprint: ExtensionBlueprint<{ config: {}; configInput: {}; dataRefs: { - translation: ConfigurableExtensionDataRef< + translation: ConfigurableExtensionDataRef_2< | TranslationResource | TranslationMessages< string, @@ -1917,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; @@ -1954,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 6c318d782a..89902d2727 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/AppTreeApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/AppTreeApi.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { createApiRef } from '@backstage/core-plugin-api'; +import { createApiRef } from '../system'; import { FrontendPlugin, Extension, ExtensionDataRef } from '../../wiring'; import { ExtensionAttachTo } from '../../wiring/resolveExtensionDefinition'; diff --git a/packages/frontend-plugin-api/src/apis/definitions/ConfigApi.ts b/packages/frontend-plugin-api/src/apis/definitions/ConfigApi.ts index 968a2843ba..f935dfa3af 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 type { 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 988c5a15c0..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', () => { 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/AppNodeProvider.test.tsx b/packages/frontend-plugin-api/src/components/AppNodeProvider.test.tsx index fcc3b74c59..0a73f90dde 100644 --- a/packages/frontend-plugin-api/src/components/AppNodeProvider.test.tsx +++ b/packages/frontend-plugin-api/src/components/AppNodeProvider.test.tsx @@ -70,12 +70,10 @@ describe('AppNodeProvider', () => { ).toThrow('AppNodeContext v1 not available'); }); expect(error).toEqual([ - expect.objectContaining({ - detail: new Error('AppNodeContext v1 not available'), - }), - expect.objectContaining({ - detail: new Error('AppNodeContext v1 not available'), - }), + expect.stringContaining('Error: AppNodeContext v1 not available'), + expect.objectContaining({ type: 'unhandled-exception' }), + expect.stringContaining('Error: AppNodeContext v1 not available'), + expect.objectContaining({ type: 'unhandled-exception' }), expect.stringContaining( 'The above error occurred in the component:', ), 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/schema/createSchemaFromZod.ts b/packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts index 85f9d5c44a..b15ff980ec 100644 --- a/packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts +++ b/packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts @@ -15,7 +15,7 @@ */ import { JsonObject } from '@backstage/types'; -import { z, ZodSchema, ZodTypeDef } from 'zod'; +import { z, type ZodSchema, type ZodTypeDef } from 'zod'; import zodToJsonSchema from 'zod-to-json-schema'; import { PortableSchema } from './types'; 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.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index 9b55505056..6e4aba2910 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -26,7 +26,7 @@ import { } from '@internal/frontend'; import { ExtensionDataRef, ExtensionDataValue } from './createExtensionDataRef'; import { ExtensionInput } from './createExtensionInput'; -import { z } from 'zod'; +import type { z } from 'zod'; import { createSchemaFromZod } from '../schema/createSchemaFromZod'; import { OpaqueExtensionDefinition } from '@internal/frontend'; import { ExtensionDataContainer } from './types'; diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index 70b6209ae3..a7537d27e9 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -26,7 +26,7 @@ import { ctxParamsSymbol, VerifyExtensionAttachTo, } from './createExtension'; -import { z } from 'zod'; +import type { z } from 'zod'; import { ExtensionInput } from './createExtensionInput'; import { ExtensionDataRef, ExtensionDataValue } from './createExtensionDataRef'; import { createExtensionDataContainer } from '@internal/frontend'; @@ -249,8 +249,8 @@ export interface ExtensionBlueprint< }, UFactoryOutput extends ExtensionDataValue, UNewOutput extends ExtensionDataRef, - TExtraInputs extends { [inputName in string]: ExtensionInput }, UParentInputs extends ExtensionDataRef, + TExtraInputs extends { [inputName in string]: ExtensionInput } = {}, >(args: { name?: TName; attachTo?: ExtensionDefinitionAttachTo & @@ -304,26 +304,30 @@ export interface ExtensionBlueprint< UFactoryOutput >; }): OverridableExtensionDefinition<{ - 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] + 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']; diff --git a/packages/frontend-test-utils/CHANGELOG.md b/packages/frontend-test-utils/CHANGELOG.md index 64b60b8a5e..c5399dc2e2 100644 --- a/packages/frontend-test-utils/CHANGELOG.md +++ b/packages/frontend-test-utils/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/frontend-test-utils +## 0.4.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.3.3 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/test-utils@1.7.14 + - @backstage/frontend-app-api@0.13.3 + +## 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 diff --git a/packages/frontend-test-utils/package.json b/packages/frontend-test-utils/package.json index 3c0c5098a7..d6b63b22da 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", + "version": "0.4.2", "backstage": { "role": "web-library" }, diff --git a/packages/integration-react/CHANGELOG.md b/packages/integration-react/CHANGELOG.md index 55dc0ca100..e7becc9337 100644 --- a/packages/integration-react/CHANGELOG.md +++ b/packages/integration-react/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/integration-react +## 1.2.13 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/core-plugin-api@1.12.1 + +## 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 diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json index 3834dd7ff6..9879042ef1 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", + "version": "1.2.13", "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 6afb2d8ffb..b732447310 100644 --- a/packages/integration/CHANGELOG.md +++ b/packages/integration/CHANGELOG.md @@ -1,5 +1,54 @@ # @backstage/integration +## 1.19.0 + +### Minor Changes + +- 37fba1d: Added support for Bitbucket Cloud OAuth. This introduces an alternative authentication method using a workspace OAuth consumer, alongside App Passwords (deprecated) and API tokens. OAuth does not require a bot or service account and avoids token expiry issues. + + **BREAKING CHANGES** + + - **@backstage/integration** (`src/bitbucketCloud/core.ts`) + + - `getBitbucketCloudRequestOptions` now returns a `Promise` and **must** be awaited. + + - **@backstage/plugin-scaffolder-backend-module-bitbucket-cloud** (`src/actions/helpers.ts`) + - `getBitbucketClient` now returns a `Promise` and **must** be awaited. + - `getAuthorizationHeader` now returns a `Promise` and **must** be awaited. + + **OAuth usage example** + + ```yaml + integrations: + bitbucketCloud: + - clientId: client-id + clientSecret: client-secret + ``` + +### Patch Changes + +- a26a322: Added support for using a GitHub App installation to generate tokens for public repository access when the `publicAccess` option is enabled. When all other authentication methods fail (e.g., the app is not installed in that organization), the provider will now use an available installation to generate a token that can be used to access public repositories as read only. +- fb029b6: Updated luxon types +- e15fdae: Made the github urls case insensitive. + +## 1.18.3-next.1 + +### Patch Changes + +- fb029b6: Updated luxon types +- Updated dependencies + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 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 diff --git a/packages/integration/config.d.ts b/packages/integration/config.d.ts index a601d63472..4278a8fcfe 100644 --- a/packages/integration/config.d.ts +++ b/packages/integration/config.d.ts @@ -174,7 +174,7 @@ export interface Config { * The username to use for authenticated requests. * @visibility secret */ - username: string; + username?: string; /** * Token used to authenticate requests. * @visibility secret @@ -186,6 +186,16 @@ export interface Config { * @deprecated Use `token` instead. */ appPassword?: string; + /** + * OAuth client ID for Bitbucket Cloud. + * @visibility secret + */ + clientId?: string; + /** + * OAuth client secret for Bitbucket Cloud. + * @visibility secret + */ + clientSecret?: string; /** * PGP signing key for signing commits. * @visibility secret @@ -330,6 +340,10 @@ export interface Config { * https://docs.github.com/en/rest/reference/apps#list-installations-for-the-authenticated-app--code-samples */ allowedInstallationOwners?: string[]; + /** + * If true, then an installation token will be issued for access when no other token is available. + */ + publicAccess?: boolean; }>; }>; diff --git a/packages/integration/package.json b/packages/integration/package.json index 3e3f792926..a7f65efc9f 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration", - "version": "1.18.2", + "version": "1.19.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/report.api.md b/packages/integration/report.api.md index f642058ccd..561dea979d 100644 --- a/packages/integration/report.api.md +++ b/packages/integration/report.api.md @@ -250,6 +250,8 @@ export type BitbucketCloudIntegrationConfig = { username?: string; appPassword?: string; token?: string; + clientId?: string; + clientSecret?: string; commitSigningKey?: string; }; @@ -450,12 +452,18 @@ export function getBitbucketCloudFileFetchUrl( config: BitbucketCloudIntegrationConfig, ): string; +// @public +export function getBitbucketCloudOAuthToken( + clientId: string, + clientSecret: string, +): Promise; + // @public export function getBitbucketCloudRequestOptions( config: BitbucketCloudIntegrationConfig, -): { +): Promise<{ headers: Record; -}; +}>; // @public @deprecated export function getBitbucketDefaultBranch( @@ -665,6 +673,7 @@ export type GithubAppConfig = { clientId: string; clientSecret: string; allowedInstallationOwners?: string[]; + publicAccess?: boolean; }; // @public diff --git a/packages/integration/src/azure/core.test.ts b/packages/integration/src/azure/core.test.ts index 601057e499..fbd6747f56 100644 --- a/packages/integration/src/azure/core.test.ts +++ b/packages/integration/src/azure/core.test.ts @@ -73,7 +73,7 @@ describe('azure core', () => { }, { url: 'com/a/b/blob/master/path/to/c.yaml', - error: 'Invalid URL: com/a/b/blob/master/path/to/c.yaml', + error: 'Invalid URL', }, ])('should handle error path %#', ({ url, error }) => { expect(() => getAzureFileFetchUrl(url)).toThrow(error); diff --git a/packages/integration/src/bitbucketCloud/config.test.ts b/packages/integration/src/bitbucketCloud/config.test.ts index 9488c8b5f7..57e8803a14 100644 --- a/packages/integration/src/bitbucketCloud/config.test.ts +++ b/packages/integration/src/bitbucketCloud/config.test.ts @@ -85,6 +85,39 @@ describe('readBitbucketCloudIntegrationConfig', () => { ).toThrow(/token/); }); + it('reads OAuth configuration', () => { + const output = readBitbucketCloudIntegrationConfig( + buildConfig({ + clientId: 'my-client-id', + clientSecret: 'my-client-secret', + }), + ); + expect(output).toEqual({ + host: BITBUCKET_CLOUD_HOST, + apiBaseUrl: BITBUCKET_CLOUD_API_BASE_URL, + clientId: 'my-client-id', + clientSecret: 'my-client-secret', + }); + }); + + it('rejects incomplete OAuth configuration', () => { + expect(() => + readBitbucketCloudIntegrationConfig( + buildConfig({ + clientId: 'my-client-id', + }), + ), + ).toThrow(/incomplete OAuth configuration/); + + expect(() => + readBitbucketCloudIntegrationConfig( + buildConfig({ + clientSecret: 'my-client-secret', + }), + ), + ).toThrow(/incomplete OAuth configuration/); + }); + it('credentials hidden on the frontend', async () => { const frontendConfig = await buildFrontendConfig({ token: 't', @@ -103,6 +136,24 @@ describe('readBitbucketCloudIntegrationConfig', () => { ]); }); + it('OAuth credentials hidden on the frontend', async () => { + const frontendConfig = await buildFrontendConfig({ + clientId: 'my-client-id', + clientSecret: 'my-client-secret', + }); + expect( + readBitbucketCloudIntegrationConfigs( + frontendConfig.getOptionalConfigArray('integrations.bitbucketCloud') ?? + [], + ), + ).toEqual([ + { + 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', () => { diff --git a/packages/integration/src/bitbucketCloud/config.ts b/packages/integration/src/bitbucketCloud/config.ts index 0e08e95a79..e47b215f98 100644 --- a/packages/integration/src/bitbucketCloud/config.ts +++ b/packages/integration/src/bitbucketCloud/config.ts @@ -54,6 +54,20 @@ export type BitbucketCloudIntegrationConfig = { */ token?: string; + /** + * The OAuth client ID for Bitbucket Cloud. + * + * See https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/ + */ + clientId?: string; + + /** + * The OAuth client secret for Bitbucket Cloud. + * + * See https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/ + */ + clientSecret?: string; + /** PGP private key for signing commits. */ commitSigningKey?: string; }; @@ -71,24 +85,36 @@ export function readBitbucketCloudIntegrationConfig( const apiBaseUrl = BITBUCKET_CLOUD_API_BASE_URL; // If config is provided, we assume authenticated access is desired // (as the anonymous one is provided by default). - const username = config.getString('username'); + const username = config.getOptionalString('username'); // 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'); + const clientId = config.getOptionalString('clientId')?.trim(); + const clientSecret = config.getOptionalString('clientSecret')?.trim(); - if (!token && !appPassword) { + // Validate: if username is provided, token or appPassword is required + if (username && !token && !appPassword) { throw new Error( `Bitbucket Cloud integration must be configured with as username and either a token or an appPassword.`, ); } + // Validate: OAuth requires both clientId and clientSecret + if ((clientId && !clientSecret) || (clientSecret && !clientId)) { + throw new Error( + `Bitbucket Cloud integration has incomplete OAuth configuration. Both clientId and clientSecret are required.`, + ); + } + return { host, apiBaseUrl, username, appPassword, token, + clientId, + clientSecret, commitSigningKey: config.getOptionalString('commitSigningKey'), }; } diff --git a/packages/integration/src/bitbucketCloud/core.test.ts b/packages/integration/src/bitbucketCloud/core.test.ts index fa8fc2ae43..4f46b5ad42 100644 --- a/packages/integration/src/bitbucketCloud/core.test.ts +++ b/packages/integration/src/bitbucketCloud/core.test.ts @@ -28,13 +28,15 @@ import { // Mock constants const BITBUCKET_CLOUD_HOST = 'bitbucket.org'; const BITBUCKET_CLOUD_API_BASE_URL = 'https://api.bitbucket.org/2.0'; +const BITBUCKET_CLOUD_OAUTH_TOKEN_URL = + 'https://bitbucket.org/site/oauth2/access_token'; describe('bitbucketCloud core', () => { const worker = setupServer(); registerMswTestHooks(worker); describe('getBitbucketCloudRequestOptions', () => { - it('insert basic auth when needed', () => { + it('insert basic auth when needed', async () => { const withUsernameAndToken: BitbucketCloudIntegrationConfig = { host: BITBUCKET_CLOUD_HOST, apiBaseUrl: BITBUCKET_CLOUD_API_BASE_URL, @@ -54,20 +56,72 @@ describe('bitbucketCloud core', () => { apiBaseUrl: BITBUCKET_CLOUD_API_BASE_URL, }; expect( - (getBitbucketCloudRequestOptions(withUsernameAndToken).headers as any) + (await getBitbucketCloudRequestOptions(withUsernameAndToken)).headers .Authorization, ).toEqual('Basic c29tZS11c2VyQGRvbWFpbi5jb206bXktdG9rZW4='); expect( - ( - getBitbucketCloudRequestOptions(withUsernameAndPassword) - .headers as any - ).Authorization, + (await getBitbucketCloudRequestOptions(withUsernameAndPassword)).headers + .Authorization, ).toEqual('Basic c29tZS11c2VyOm15LXNlY3JldA=='); expect( - (getBitbucketCloudRequestOptions(withoutUsername).headers as any) + (await getBitbucketCloudRequestOptions(withoutUsername)).headers .Authorization, ).toBeUndefined(); }); + + it('handles OAuth token fetch errors', async () => { + // Test error handling + worker.use( + rest.post(BITBUCKET_CLOUD_OAUTH_TOKEN_URL, (_, res, ctx) => + res(ctx.status(401), ctx.json({ error: 'invalid_client' })), + ), + ); + + const withOAuth: BitbucketCloudIntegrationConfig = { + host: BITBUCKET_CLOUD_HOST, + apiBaseUrl: BITBUCKET_CLOUD_API_BASE_URL, + clientId: 'test-client-id', + clientSecret: 'test-client-secret', + }; + + await expect(getBitbucketCloudRequestOptions(withOAuth)).rejects.toThrow( + /Failed to fetch OAuth token/, + ); + }); + + it('uses OAuth Bearer token and caches it', async () => { + // Test OAuth + caching + let callCount = 0; + worker.use( + rest.post(BITBUCKET_CLOUD_OAUTH_TOKEN_URL, (_, res, ctx) => { + callCount++; + return res( + ctx.status(200), + ctx.json({ + access_token: 'test-oauth-token', + expires_in: 3600, + }), + ); + }), + ); + + const withOAuth: BitbucketCloudIntegrationConfig = { + host: BITBUCKET_CLOUD_HOST, + apiBaseUrl: BITBUCKET_CLOUD_API_BASE_URL, + clientId: 'test-client-id', + clientSecret: 'test-client-secret', + }; + + // First call should fetch token + const result1 = await getBitbucketCloudRequestOptions(withOAuth); + expect(result1.headers.Authorization).toEqual('Bearer test-oauth-token'); + expect(callCount).toBe(1); + + // Second call should use cached token (proves caching works) + const result2 = await getBitbucketCloudRequestOptions(withOAuth); + expect(result2.headers.Authorization).toEqual('Bearer test-oauth-token'); + expect(callCount).toBe(1); // Still 1, proving cache was used + }); }); describe('getBitbucketCloudFileFetchUrl', () => { diff --git a/packages/integration/src/bitbucketCloud/core.ts b/packages/integration/src/bitbucketCloud/core.ts index a97de872de..9beb3bd748 100644 --- a/packages/integration/src/bitbucketCloud/core.ts +++ b/packages/integration/src/bitbucketCloud/core.ts @@ -17,6 +17,100 @@ import fetch from 'cross-fetch'; import parseGitUrl from 'git-url-parse'; import { BitbucketCloudIntegrationConfig } from './config'; +import { DateTime } from 'luxon'; + +type OAuthTokenData = { + token: string; + expiresAt: DateTime; +}; + +// In-memory token cache (single entry since there's only one Bitbucket Cloud integration) +let cachedToken: OAuthTokenData | undefined; + +// Track in-flight token refresh request to prevent concurrent fetches +let refreshPromise: Promise | undefined; + +/** + * Fetches an OAuth access token from Bitbucket Cloud using client credentials flow. + * Tokens are cached with a 10-minute grace period to account for clock skew. + * Implements concurrent refresh protection to prevent multiple simultaneous token requests. + * + * @param clientId - OAuth client ID + * @param clientSecret - OAuth client secret + * @public + */ +export async function getBitbucketCloudOAuthToken( + clientId: string, + clientSecret: string, +): Promise { + // Check cache + if (cachedToken && DateTime.now() < cachedToken.expiresAt) { + return cachedToken.token; + } + + // Check if there's already a refresh in progress + if (refreshPromise) { + return refreshPromise; + } + + // Start a new token fetch and track it + refreshPromise = (async () => { + try { + // Fetch new token + const credentials = Buffer.from( + `${clientId}:${clientSecret}`, + 'utf8', + ).toString('base64'); + + const response = await fetch( + 'https://bitbucket.org/site/oauth2/access_token', + { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Authorization: `Basic ${credentials}`, + }, + body: 'grant_type=client_credentials', + }, + ); + + if (!response.ok) { + throw new Error( + `Failed to fetch OAuth token from Bitbucket Cloud: ${response.status} ${response.statusText}`, + ); + } + + const data = await response.json(); + + if (!data.access_token) { + throw new Error('OAuth token response missing access_token field'); + } + + // Calculate expiration with 10-minute grace period + const expiresIn = data.expires_in || 3600; // Default to 1 hour + const expiresAt = DateTime.now() + .plus({ seconds: expiresIn }) + .minus({ minutes: 10 }); + + // Cache the token + cachedToken = { + token: data.access_token, + expiresAt, + }; + + return data.access_token; + } catch (error) { + throw new Error( + `Failed to fetch OAuth token for Bitbucket Cloud: ${error}`, + ); + } finally { + // Clean up the in-flight promise tracking + refreshPromise = undefined; + } + })(); + + return refreshPromise; +} /** * Given a URL pointing to a path on a provider, returns the default branch. @@ -34,7 +128,7 @@ export async function getBitbucketCloudDefaultBranch( const branchUrl = `${config.apiBaseUrl}/repositories/${project}/${repoName}`; const response = await fetch( branchUrl, - getBitbucketCloudRequestOptions(config), + await getBitbucketCloudRequestOptions(config), ); if (!response.ok) { @@ -118,18 +212,29 @@ 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. + * Supports OAuth (clientId/clientSecret), username/token, and username/appPassword auth. * * @param config - The relevant provider config * @public */ -export function getBitbucketCloudRequestOptions( +export async function getBitbucketCloudRequestOptions( config: BitbucketCloudIntegrationConfig, -): { +): Promise<{ headers: Record; -} { +}> { const headers: Record = {}; + // OAuth authentication (clientId/clientSecret) + if (config.clientId && config.clientSecret) { + const token = await getBitbucketCloudOAuthToken( + config.clientId, + config.clientSecret, + ); + headers.Authorization = `Bearer ${token}`; + return { headers }; + } + + // Basic authentication (username + token/appPassword) // TODO: appPassword can be removed once fully // deprecated by BitBucket on 9th June 2026. if (config.username && (config.token ?? config.appPassword)) { diff --git a/packages/integration/src/bitbucketCloud/index.ts b/packages/integration/src/bitbucketCloud/index.ts index 7119d3ea90..d0c11136b3 100644 --- a/packages/integration/src/bitbucketCloud/index.ts +++ b/packages/integration/src/bitbucketCloud/index.ts @@ -24,5 +24,6 @@ export { getBitbucketCloudDefaultBranch, getBitbucketCloudDownloadUrl, getBitbucketCloudFileFetchUrl, + getBitbucketCloudOAuthToken, getBitbucketCloudRequestOptions, } from './core'; 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/integration/src/github/SingleInstanceGithubCredentialsProvider.test.ts b/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.test.ts index 0468e96e14..fefccf22f6 100644 --- a/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.test.ts +++ b/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.test.ts @@ -563,4 +563,330 @@ describe('SingleInstanceGithubCredentialsProvider tests', () => { 2, ); }); + + describe('public access', () => { + it('should use an installation token when public access is enabled and owner is not in allowed list', async () => { + const githubProvider = SingleInstanceGithubCredentialsProvider.create({ + host: 'github.com', + apps: [ + { + appId: 1, + privateKey: 'privateKey', + webhookSecret: '123', + clientId: 'CLIENT_ID', + clientSecret: 'CLIENT_SECRET', + allowedInstallationOwners: ['other-org'], + publicAccess: true, + }, + ], + }); + + octokit.apps.listInstallations.mockResolvedValue({ + headers: { + etag: '123', + }, + data: [ + { + id: 1, + repository_selection: 'all', + account: { + login: 'other-org', + }, + }, + ], + } as RestEndpointMethodTypes['apps']['listInstallations']['response']); + + octokit.apps.createInstallationAccessToken.mockResolvedValueOnce({ + data: { + expires_at: DateTime.local().plus({ hours: 1 }).toString(), + token: 'public_access_token', + }, + } as RestEndpointMethodTypes['apps']['createInstallationAccessToken']['response']); + + const { token, headers, type } = await githubProvider.getCredentials({ + url: 'https://github.com/some-public-org/some-repo', + }); + + expect(type).toEqual('app'); + expect(token).toEqual('public_access_token'); + expect(headers).toEqual({ Authorization: 'Bearer public_access_token' }); + }); + + it('should use an installation token when public access is enabled and no installation exists for owner', async () => { + const githubProvider = SingleInstanceGithubCredentialsProvider.create({ + host: 'github.com', + apps: [ + { + appId: 1, + privateKey: 'privateKey', + webhookSecret: '123', + clientId: 'CLIENT_ID', + clientSecret: 'CLIENT_SECRET', + publicAccess: true, + }, + ], + }); + + octokit.apps.listInstallations.mockResolvedValue({ + headers: { + etag: '123', + }, + data: [ + { + id: 42, + repository_selection: 'all', + account: { + login: 'installed-org', + }, + }, + ], + } as RestEndpointMethodTypes['apps']['listInstallations']['response']); + + octokit.apps.createInstallationAccessToken.mockResolvedValueOnce({ + data: { + expires_at: DateTime.local().plus({ hours: 1 }).toString(), + token: 'public_installation_token', + }, + } as RestEndpointMethodTypes['apps']['createInstallationAccessToken']['response']); + + const { token, headers, type } = await githubProvider.getCredentials({ + url: 'https://github.com/non-installed-org/some-repo', + }); + + expect(type).toEqual('app'); + expect(token).toEqual('public_installation_token'); + expect(headers).toEqual({ + Authorization: 'Bearer public_installation_token', + }); + }); + + it('should not use public access when normal installation credentials are available', async () => { + const githubProvider = SingleInstanceGithubCredentialsProvider.create({ + host: 'github.com', + apps: [ + { + appId: 1, + privateKey: 'privateKey', + webhookSecret: '123', + clientId: 'CLIENT_ID', + clientSecret: 'CLIENT_SECRET', + publicAccess: true, + }, + ], + }); + + octokit.apps.listInstallations.mockResolvedValue({ + headers: { + etag: '123', + }, + data: [ + { + id: 1, + repository_selection: 'all', + account: { + login: 'backstage', + }, + }, + ], + } as RestEndpointMethodTypes['apps']['listInstallations']['response']); + + octokit.apps.createInstallationAccessToken.mockResolvedValueOnce({ + data: { + expires_at: DateTime.local().plus({ hours: 1 }).toString(), + token: 'normal_token', + }, + } as RestEndpointMethodTypes['apps']['createInstallationAccessToken']['response']); + + const { token, type } = await githubProvider.getCredentials({ + url: 'https://github.com/backstage/repo', + }); + + expect(type).toEqual('app'); + expect(token).toEqual('normal_token'); + // createInstallationAccessToken should only be called once for the normal flow + expect(octokit.apps.createInstallationAccessToken).toHaveBeenCalledTimes( + 1, + ); + }); + + it('should fall back to configured token when public access fails', async () => { + const githubProvider = SingleInstanceGithubCredentialsProvider.create({ + host: 'github.com', + apps: [ + { + appId: 1, + privateKey: 'privateKey', + webhookSecret: '123', + clientId: 'CLIENT_ID', + clientSecret: 'CLIENT_SECRET', + allowedInstallationOwners: ['other-org'], + publicAccess: true, + }, + ], + token: 'fallback_token', + }); + + octokit.apps.listInstallations.mockResolvedValue({ + headers: { + etag: '123', + }, + data: [], + } as unknown as RestEndpointMethodTypes['apps']['listInstallations']['response']); + + const { token, type } = await githubProvider.getCredentials({ + url: 'https://github.com/some-org/repo', + }); + + expect(type).toEqual('token'); + expect(token).toEqual('fallback_token'); + }); + + it('should return undefined when public access is disabled and no installation exists', async () => { + const githubProvider = SingleInstanceGithubCredentialsProvider.create({ + host: 'github.com', + apps: [ + { + appId: 1, + privateKey: 'privateKey', + webhookSecret: '123', + clientId: 'CLIENT_ID', + clientSecret: 'CLIENT_SECRET', + allowedInstallationOwners: ['other-org'], + // publicAccess is not set (defaults to false) + }, + ], + }); + + octokit.apps.listInstallations.mockResolvedValue({ + headers: { + etag: '123', + }, + data: [ + { + id: 1, + repository_selection: 'all', + account: { + login: 'other-org', + }, + }, + ], + } as RestEndpointMethodTypes['apps']['listInstallations']['response']); + + const { token, headers } = await githubProvider.getCredentials({ + url: 'https://github.com/some-org/repo', + }); + + expect(token).toBeUndefined(); + expect(headers).toBeUndefined(); + }); + + it('should cache public access tokens separately from regular tokens', async () => { + const githubProvider = SingleInstanceGithubCredentialsProvider.create({ + host: 'github.com', + apps: [ + { + appId: 1, + privateKey: 'privateKey', + webhookSecret: '123', + clientId: 'CLIENT_ID', + clientSecret: 'CLIENT_SECRET', + allowedInstallationOwners: ['installed-org'], + publicAccess: true, + }, + ], + }); + + octokit.apps.listInstallations.mockResolvedValue({ + headers: { + etag: '123', + }, + data: [ + { + id: 1, + repository_selection: 'all', + account: { + login: 'installed-org', + }, + }, + ], + } as RestEndpointMethodTypes['apps']['listInstallations']['response']); + + octokit.apps.createInstallationAccessToken.mockResolvedValue({ + data: { + expires_at: DateTime.local().plus({ hours: 1 }).toString(), + token: 'public_token', + }, + } as RestEndpointMethodTypes['apps']['createInstallationAccessToken']['response']); + + // First call for a public org + await githubProvider.getCredentials({ + url: 'https://github.com/public-org/repo', + }); + + // Second call for the same public org should use cached token + await githubProvider.getCredentials({ + url: 'https://github.com/public-org/repo', + }); + + // createInstallationAccessToken should only be called once due to caching + expect(octokit.apps.createInstallationAccessToken).toHaveBeenCalledTimes( + 1, + ); + }); + + it('should use public access with multiple apps when only one has publicAccess enabled', async () => { + const githubProvider = SingleInstanceGithubCredentialsProvider.create({ + host: 'github.com', + apps: [ + { + appId: 1, + privateKey: 'privateKey', + webhookSecret: '123', + clientId: 'CLIENT_ID', + clientSecret: 'CLIENT_SECRET', + allowedInstallationOwners: ['org-1'], + // publicAccess not set, defaults to false + }, + { + appId: 2, + privateKey: 'privateKey2', + webhookSecret: '456', + clientId: 'CLIENT_ID_2', + clientSecret: 'CLIENT_SECRET_2', + allowedInstallationOwners: ['org-2'], + publicAccess: true, + }, + ], + }); + + octokit.apps.listInstallations.mockResolvedValue({ + headers: { + etag: '123', + }, + data: [ + { + id: 1, + repository_selection: 'all', + account: { + login: 'org-2', + }, + }, + ], + } as RestEndpointMethodTypes['apps']['listInstallations']['response']); + + octokit.apps.createInstallationAccessToken.mockResolvedValueOnce({ + data: { + expires_at: DateTime.local().plus({ hours: 1 }).toString(), + token: 'public_access_from_app_2', + }, + } as RestEndpointMethodTypes['apps']['createInstallationAccessToken']['response']); + + const { token, type } = await githubProvider.getCredentials({ + url: 'https://github.com/unknown-org/repo', + }); + + expect(type).toEqual('app'); + expect(token).toEqual('public_access_from_app_2'); + }); + }); }); diff --git a/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts b/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts index d2c1e37a65..40d166b223 100644 --- a/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts +++ b/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts @@ -100,6 +100,7 @@ class GithubAppManager { private readonly baseAuthConfig: { appId: number; privateKey: string }; private readonly cache = new Cache(); private readonly allowedInstallationOwners: string[] | undefined; // undefined allows all installations + public readonly publicAccess: boolean; constructor(config: GithubAppConfig, baseUrl?: string) { this.allowedInstallationOwners = config.allowedInstallationOwners?.map( @@ -116,6 +117,7 @@ class GithubAppManager { authStrategy: createAppAuth, auth: this.baseAuthConfig, }); + this.publicAccess = config.publicAccess ?? false; } async getInstallationCredentials( @@ -170,6 +172,30 @@ class GithubAppManager { }); } + async getPublicInstallationToken(): Promise<{ accessToken: string }> { + const [installation] = await this.getInstallations(); + + if (!installation) { + throw new Error(`No installation found for public app`); + } + + return this.cache.getOrCreateToken( + `public:${installation.id}`, + undefined, + async () => { + const result = await this.appClient.apps.createInstallationAccessToken({ + installation_id: installation.id, + headers: HEADERS, + }); + + return { + token: result.data.token, + expiresAt: DateTime.fromISO(result.data.expires_at), + }; + }, + ); + } + getInstallations(): Promise< RestEndpointMethodTypes['apps']['listInstallations']['response']['data'] > { @@ -185,12 +211,14 @@ class GithubAppManager { inst.account.login?.toLocaleLowerCase('en-US') === owner.toLocaleLowerCase('en-US'), ); + if (installation) { return { installationId: installation.id, suspended: Boolean(installation.suspended_by), }; } + const notFoundError = new Error( `No app installation found for ${owner} in ${this.baseAuthConfig.appId}`, ); @@ -245,10 +273,26 @@ export class GithubAppCredentialsMux { const result = results.find( resultItem => resultItem.credentials?.accessToken, ); + if (result) { return result.credentials!.accessToken; } + // If there was no token returned, then let's find a public access app and use an installation to get a token. + const publicAccessApp = this.apps.find(app => app.publicAccess); + if (publicAccessApp) { + const publicResult = await publicAccessApp + .getPublicInstallationToken() + .then( + credentials => ({ credentials, error: undefined }), + error => ({ credentials: undefined, error }), + ); + + if (publicResult.credentials?.accessToken) { + return publicResult.credentials.accessToken; + } + } + const errors = results.map(r => r.error); const notNotFoundError = errors.find(err => err?.name !== 'NotFoundError'); if (notNotFoundError) { diff --git a/packages/integration/src/github/config.ts b/packages/integration/src/github/config.ts index 74a0267d62..f755b84c2a 100644 --- a/packages/integration/src/github/config.ts +++ b/packages/integration/src/github/config.ts @@ -109,6 +109,10 @@ export type GithubAppConfig = { * https://docs.github.com/en/rest/reference/apps#list-installations-for-the-authenticated-app--code-samples */ allowedInstallationOwners?: string[]; + /** + * If true, then an installation token will be issued for access when no other token is available. + */ + publicAccess?: boolean; }; /** @@ -133,6 +137,7 @@ export function readGithubIntegrationConfig( allowedInstallationOwners: c.getOptionalStringArray( 'allowedInstallationOwners', ), + publicAccess: c.getOptionalBoolean('publicAccess'), })); if (!isValidHost(host)) { diff --git a/packages/repo-tools/CHANGELOG.md b/packages/repo-tools/CHANGELOG.md index 18eaffac1b..80904e4d06 100644 --- a/packages/repo-tools/CHANGELOG.md +++ b/packages/repo-tools/CHANGELOG.md @@ -1,5 +1,69 @@ # @backstage/repo-tools +## 0.16.1 + +### Patch Changes + +- 688f070: Updated to use new utilities from `@backstage/cli-common`. +- 85895f9: Updates OpenAPI generator templates to preserve original property names (like 'group-name', 'user-id') from OpenAPI specs when propertyNaming=original is specified. Previously, these were always converted to camelCase regardless of the propertyNaming setting. + + - Updates modelGeneric.mustache templates in both client and server generators + - Updates modelTaggedUnion.mustache templates in both client and server generators + - Uses {{baseName}} when available, falls back to {{name}} for backward compatibility + - Maintains backward compatibility - no changes when propertyNaming=original is not used + +- 2bae83a: Bump `@microsoft/api-documenter` and `@microsoft/api-extractor` to latest versions. +- d1e38a7: Properly create workspace in OS temporary directory for `generate-patch` command +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/cli-node@0.2.16 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/cli-common@0.1.16 + - @backstage/config-loader@1.10.7 + +## 0.16.1-next.2 + +### Patch Changes + +- 2bae83a: Bump `@microsoft/api-documenter` and `@microsoft/api-extractor` to latest versions. +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config-loader@1.10.7-next.1 + - @backstage/cli-common@0.1.16-next.2 + - @backstage/catalog-model@1.7.6 + - @backstage/cli-node@0.2.16-next.1 + - @backstage/errors@1.2.7 + +## 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 diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index 2d0e312c4e..8cf1b48d06 100644 --- a/packages/repo-tools/package.json +++ b/packages/repo-tools/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/repo-tools", - "version": "0.16.0", + "version": "0.16.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/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 87bc2660f6..35db413d8f 100644 --- a/packages/scaffolder-internal/CHANGELOG.md +++ b/packages/scaffolder-internal/CHANGELOG.md @@ -1,5 +1,21 @@ # @internal/scaffolder +## 0.0.16 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/plugin-scaffolder-react@1.19.4 + +## 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 diff --git a/packages/scaffolder-internal/package.json b/packages/scaffolder-internal/package.json index b3a074a331..98d76bb84e 100644 --- a/packages/scaffolder-internal/package.json +++ b/packages/scaffolder-internal/package.json @@ -1,6 +1,6 @@ { "name": "@internal/scaffolder", - "version": "0.0.15", + "version": "0.0.16", "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 8c91814bdf..6542287948 100644 --- a/packages/techdocs-cli-embedded-app/CHANGELOG.md +++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md @@ -1,5 +1,73 @@ # techdocs-cli-embedded-app +## 0.2.115 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/cli@0.35.0 + - @backstage/ui@0.10.0 + - @backstage/plugin-techdocs@1.16.1 + - @backstage/plugin-catalog@1.32.1 + - @backstage/core-components@0.18.4 + - @backstage/test-utils@1.7.14 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/app-defaults@1.7.3 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-techdocs-react@1.3.6 + +## 0.2.115-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3-next.1 + - @backstage/cli@0.35.0-next.2 + - @backstage/plugin-techdocs@1.16.1-next.2 + - @backstage/plugin-catalog@1.32.1-next.1 + - @backstage/core-components@0.18.4-next.2 + - @backstage/app-defaults@1.7.3-next.0 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/test-utils@1.7.14-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/ui@0.10.0-next.1 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + +## 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 diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index 44e183f47e..e8db7e68d5 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", + "version": "0.2.115", "backstage": { "role": "frontend" }, diff --git a/packages/techdocs-cli-embedded-app/src/LiveReloadAddon.test.tsx b/packages/techdocs-cli-embedded-app/src/LiveReloadAddon.test.tsx index 1a80def9e9..ec99f405ae 100644 --- a/packages/techdocs-cli-embedded-app/src/LiveReloadAddon.test.tsx +++ b/packages/techdocs-cli-embedded-app/src/LiveReloadAddon.test.tsx @@ -15,7 +15,7 @@ */ import { render } from '@testing-library/react'; -import { TechDocsLiveReload } from './LiveReloadAddon'; +import { TechDocsLiveReload, utils } from './LiveReloadAddon'; jest.mock('@backstage/plugin-techdocs-react', () => ({ useShadowRootElements: jest.fn(() => [ @@ -38,12 +38,11 @@ jest.mock('@backstage/plugin-techdocs-react', () => ({ describe('TechDocsLiveReload', () => { const originalXHR = global.XMLHttpRequest; - let originalLocation: Location; let openSpy: jest.Mock; let sendSpy: jest.Mock; + let reloadPageSpy: jest.SpyInstance; beforeEach(() => { - originalLocation = window.location; openSpy = jest.fn(); sendSpy = jest.fn(function (this: any) { // simulate long-poll response that does NOT trigger reload (epoch unchanged) @@ -65,9 +64,11 @@ describe('TechDocsLiveReload', () => { global.XMLHttpRequest = MockXHR as any; - // Replace window.location with a mutable object for tests - delete (window as any).location; - (window as any).location = { ...originalLocation, reload: jest.fn() }; + // Spy on the utils object's reloadPage method + reloadPageSpy = jest + .spyOn(utils, 'reloadPage') + .mockImplementation(() => {}); + jest.spyOn(window, 'addEventListener').mockImplementation(() => {}); jest.spyOn(window, 'removeEventListener').mockImplementation(() => {}); Object.defineProperty(document, 'visibilityState', { @@ -79,23 +80,17 @@ describe('TechDocsLiveReload', () => { afterEach(() => { global.XMLHttpRequest = originalXHR; jest.restoreAllMocks(); - // restore original window.location - delete (window as any).location; - (window as any).location = originalLocation; }); it('polls livereload endpoint and does not reload when epoch unchanged', async () => { - const reloadSpy = window.location.reload as unknown as jest.Mock; render(); expect(openSpy).toHaveBeenCalledWith('GET', '/.livereload/10/1'); // give microtask queue a tick await new Promise(res => setTimeout(res, 0)); - expect(reloadSpy).not.toHaveBeenCalled(); + expect(reloadPageSpy).not.toHaveBeenCalled(); }); it('reloads when server epoch increases', async () => { - const reloadSpy = window.location.reload as unknown as jest.Mock; - sendSpy.mockImplementation(function (this: any) { setTimeout(() => { (this as any).status = 200; @@ -106,6 +101,6 @@ describe('TechDocsLiveReload', () => { render(); await new Promise(res => setTimeout(res, 0)); - expect(reloadSpy).toHaveBeenCalled(); + expect(reloadPageSpy).toHaveBeenCalled(); }); }); diff --git a/packages/techdocs-cli-embedded-app/src/LiveReloadAddon.tsx b/packages/techdocs-cli-embedded-app/src/LiveReloadAddon.tsx index 905a339c72..393704da97 100644 --- a/packages/techdocs-cli-embedded-app/src/LiveReloadAddon.tsx +++ b/packages/techdocs-cli-embedded-app/src/LiveReloadAddon.tsx @@ -17,6 +17,13 @@ import { useShadowRootElements } from '@backstage/plugin-techdocs-react'; import { useEffect, useRef } from 'react'; +/** @internal Exported for testing - allows spying on reloads without spying + * on window.location. + */ +export const utils = { + reloadPage: () => window.location.reload(), +}; + interface TechDocsLiveReloadProps { /** Whether to enable livereload (default: true in development) */ enabled?: boolean; @@ -69,7 +76,7 @@ export const TechDocsLiveReload = ({ reqRef.current = new XMLHttpRequest(); reqRef.current.onloadend = function handleLoadEnd(this: XMLHttpRequest) { if (parseFloat(this.responseText) > epoch) { - window.location.reload(); + utils.reloadPage(); } else { timeoutRef.current = setTimeout(poll, this.status === 200 ? 0 : 3000); } diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md index 31d49db8ab..5295128a8b 100644 --- a/packages/techdocs-cli/CHANGELOG.md +++ b/packages/techdocs-cli/CHANGELOG.md @@ -1,5 +1,47 @@ # @techdocs/cli +## 1.10.3 + +### Patch Changes + +- 43629b1: Updated to use new utilities from `@backstage/cli-common`. +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/backend-defaults@0.14.0 + - @backstage/plugin-techdocs-node@1.13.10 + - @backstage/cli-common@0.1.16 + +## 1.10.3-next.2 + +### Patch Changes + +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/plugin-techdocs-node@1.13.10-next.1 + - @backstage/cli-common@0.1.16-next.2 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + +## 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 diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index 994838b332..c27d829fe7 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -1,6 +1,6 @@ { "name": "@techdocs/cli", - "version": "1.10.2", + "version": "1.10.3", "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 4c2a959dbc..71c5aca50a 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/test-utils +## 1.7.14 + +### Patch Changes + +- 97cd16f: Internal update of translation imports. +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/plugin-permission-react@0.4.39 + +## 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 diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 8331af7f0d..2e12255bd0 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", + "version": "1.7.14", "description": "Utilities to test Backstage plugins and apps.", "backstage": { "role": "web-library" diff --git a/packages/test-utils/src/testUtils/apis/FetchApi/MockFetchApi.ts b/packages/test-utils/src/testUtils/apis/FetchApi/MockFetchApi.ts index 3d02db0124..661d202379 100644 --- a/packages/test-utils/src/testUtils/apis/FetchApi/MockFetchApi.ts +++ b/packages/test-utils/src/testUtils/apis/FetchApi/MockFetchApi.ts @@ -127,7 +127,9 @@ function baseImplementation( ): typeof crossFetch { const implementation = options?.baseImplementation; if (!implementation) { - return crossFetch; + // Return a wrapper that evaluates global.fetch at call time, not construction time. + // This allows MSW to patch global.fetch after MockFetchApi is constructed. + return (input, init) => global.fetch(input, init); } else if (implementation === 'none') { return () => Promise.resolve(new Response()); } 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/test-utils/src/testUtils/appWrappers.test.tsx b/packages/test-utils/src/testUtils/appWrappers.test.tsx index 42a3f8e092..158499374e 100644 --- a/packages/test-utils/src/testUtils/appWrappers.test.tsx +++ b/packages/test-utils/src/testUtils/appWrappers.test.tsx @@ -92,17 +92,15 @@ describe('wrapInTestApp', () => { }); expect(error).toEqual([ - expect.objectContaining({ - detail: new Error( - 'MockErrorApi received unexpected error, Error: NOPE', - ), - }), - expect.objectContaining({ - detail: new Error( - 'MockErrorApi received unexpected error, Error: NOPE', - ), - }), - expect.stringMatching(/^The above error occurred in the component:/), + expect.stringContaining( + 'Error: MockErrorApi received unexpected error, Error: NOPE', + ), + expect.objectContaining({ type: 'unhandled-exception' }), + expect.stringContaining( + 'Error: MockErrorApi received unexpected error, Error: NOPE', + ), + expect.objectContaining({ type: 'unhandled-exception' }), + expect.stringContaining('The above error occurred in the component:'), ]); }); diff --git a/packages/theme/CHANGELOG.md b/packages/theme/CHANGELOG.md index 1b5b158102..bbe9ae3fc9 100644 --- a/packages/theme/CHANGELOG.md +++ b/packages/theme/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/theme +## 0.7.1 + +### Patch Changes + +- fa06f6b: Added a `themeName` prop to `UnifiedThemeProvider`, enabling Backstage UI `data-theme-name` CSS attribute to be set based on active theme. + +## 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..645255d601 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", "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 1e56ceda40..c3c0ffb38f 100644 --- a/packages/ui/CHANGELOG.md +++ b/packages/ui/CHANGELOG.md @@ -1,5 +1,131 @@ # @backstage/ui +## 0.10.0 + +### Minor Changes + +- 16543fa: **Breaking change** The `Cell` component has been refactored to be a generic wrapper component that accepts `children` for custom cell content. The text-specific functionality (previously part of `Cell`) has been moved to a new `CellText` component. + + ### Migration Guide + + If you were using `Cell` with text-specific props (`title`, `description`, `leadingIcon`, `href`), you need to update your code to use `CellText` instead: + + **Before:** + + ```tsx + } + href="/path" + /> + ``` + + **After:** + + ```tsx + } + href="/path" + /> + ``` + + For custom cell content, use the new generic `Cell` component: + + ```tsx + {/* Your custom content */} + ``` + +### Patch Changes + +- 50b7927: Fixed Checkbox indicator showing checkmark color when unchecked. + + Affected components: Checkbox + +- 5bacf55: Fixed `ButtonIcon` incorrectly applying `className` to inner elements instead of only the root element. + + Affected components: ButtonIcon + +- b3ad928: Fixed Table Row component to correctly handle cases where no `href` is provided, preventing unnecessary router provider wrapping and fixing the cursor incorrectly showing as a pointer despite the element not being a link. + + Affected components: Row + +- a20d317: Added row selection support with visual state styling for hover, selected, and pressed states. Fixed checkbox rendering to only show for multi-select toggle mode. + + Affected components: Table, TableHeader, Row, Column + +- fe7c751: Fixed `useTable` hook to prioritize `providedRowCount` over data length for accurate row count in server-side pagination scenarios. +- c145031: Fixed Table column sorting indicator to show up arrow when no sort is active, correctly indicating that clicking will sort ascending. + + Affected components: Column + +## 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 diff --git a/packages/ui/css/styles.css b/packages/ui/css/styles.css index c96eda05c9..f79b954080 100644 --- a/packages/ui/css/styles.css +++ b/packages/ui/css/styles.css @@ -51,9 +51,10 @@ --bui-gray-6: #8c8c8c; --bui-gray-7: #757575; --bui-gray-8: #595959; - --bui-bg: var(--bui-gray-1); + --bui-bg-surface-0: var(--bui-gray-1); --bui-bg-surface-1: var(--bui-white); - --bui-bg-surface-2: var(--bui-gray-2); + --bui-bg-surface-2: var(--bui-gray-1); + --bui-bg-surface-3: var(--bui-gray-2); --bui-bg-solid: #1f5493; --bui-bg-solid-hover: #163a66; --bui-bg-solid-pressed: #0f2b4e; @@ -99,9 +100,10 @@ --bui-gray-6: #7b7b7b; --bui-gray-7: #9e9e9e; --bui-gray-8: #b4b4b4; - --bui-bg: #333; - --bui-bg-surface-1: #424242; + --bui-bg-surface-0: #333; + --bui-bg-surface-1: var(--bui-gray-1); --bui-bg-surface-2: var(--bui-gray-2); + --bui-bg-surface-3: var(--bui-gray-3); --bui-bg-solid: #9cc9ff; --bui-bg-solid-hover: #83b9fd; --bui-bg-solid-pressed: #83b9fd; @@ -239,7 +241,7 @@ } body { - background-color: var(--bui-bg); + background-color: var(--bui-bg-surface-0); color: var(--bui-fg-primary); font-family: var(--bui-font-regular); font-weight: var(--bui-font-weight-regular); diff --git a/packages/ui/package.json b/packages/ui/package.json index b034dbe59f..00b51295fd 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/ui", - "version": "0.9.0", + "version": "0.10.0", "backstage": { "role": "web-library" }, @@ -50,7 +50,7 @@ "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", "chalk": "^5.4.1", - "eslint-plugin-storybook": "^9.1.7", + "eslint-plugin-storybook": "^10.1.9", "glob": "^11.0.1", "globals": "^15.11.0", "lightningcss": "^1.29.1", @@ -58,7 +58,7 @@ "react": "^18.0.2", "react-dom": "^18.0.2", "react-router-dom": "^6.3.0", - "storybook": "^9.1.7" + "storybook": "^10.1.10" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md index 30910724eb..0a7e289d0a 100644 --- a/packages/ui/report.api.md +++ b/packages/ui/report.api.md @@ -406,7 +406,16 @@ 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?: TextColors; // (undocumented) @@ -863,12 +872,16 @@ export const LinkDefinition: { // @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>; @@ -1265,6 +1278,8 @@ export const TableDefinition: { readonly cellProfileAvatarFallback: 'bui-TableCellProfileAvatarFallback'; readonly cellProfileName: 'bui-TableCellProfileName'; readonly cellProfileLink: 'bui-TableCellProfileLink'; + readonly headSelection: 'bui-TableHeadSelection'; + readonly cellSelection: 'bui-TableCellSelection'; }; }; diff --git a/packages/ui/src/components/Accordion/Accordion.stories.tsx b/packages/ui/src/components/Accordion/Accordion.stories.tsx index 6640bd5d6a..541cce5c84 100644 --- a/packages/ui/src/components/Accordion/Accordion.stories.tsx +++ b/packages/ui/src/components/Accordion/Accordion.stories.tsx @@ -13,8 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import type { Meta, StoryObj } from '@storybook/react-vite'; +import preview from '../../../../../.storybook/preview'; import { Accordion, AccordionTrigger, @@ -36,15 +35,12 @@ const Content = () => ( ); -const meta = { +const meta = preview.meta({ title: 'Backstage UI/Accordion', component: Accordion, -} satisfies Meta; +}); -export default meta; -type Story = StoryObj; - -export const Default: Story = { +export const Default = meta.story({ render: () => ( @@ -53,9 +49,9 @@ export const Default: Story = { ), -}; +}); -export const WithSubtitle: Story = { +export const WithSubtitle = meta.story({ render: () => ( ), -}; +}); -export const CustomTrigger: Story = { +export const CustomTrigger = meta.story({ render: () => ( @@ -87,9 +83,9 @@ export const CustomTrigger: Story = { ), -}; +}); -export const DefaultExpanded: Story = { +export const DefaultExpanded = meta.story({ render: () => ( @@ -98,9 +94,9 @@ export const DefaultExpanded: Story = { ), -}; +}); -export const GroupSingleOpen: Story = { +export const GroupSingleOpen = meta.story({ render: () => ( @@ -136,9 +132,9 @@ export const GroupSingleOpen: Story = { ), -}; +}); -export const GroupMultipleOpen: Story = { +export const GroupMultipleOpen = meta.story({ render: () => ( @@ -174,4 +170,4 @@ export const GroupMultipleOpen: Story = { ), -}; +}); diff --git a/packages/ui/src/components/Avatar/Avatar.stories.tsx b/packages/ui/src/components/Avatar/Avatar.stories.tsx index 582d82393e..75c1e68a67 100644 --- a/packages/ui/src/components/Avatar/Avatar.stories.tsx +++ b/packages/ui/src/components/Avatar/Avatar.stories.tsx @@ -13,36 +13,32 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import type { Meta, StoryObj } from '@storybook/react-vite'; +import preview from '../../../../../.storybook/preview'; import { Avatar } from './index'; import { Flex, Text } from '../..'; -const meta = { +const meta = preview.meta({ title: 'Backstage UI/Avatar', component: Avatar, -} satisfies Meta; +}); -export default meta; -type Story = StoryObj; - -export const Default: Story = { +export const Default = meta.story({ args: { src: 'https://avatars.githubusercontent.com/u/1540635?v=4', name: 'Charles de Dreuille', }, -}; +}); -export const Fallback: Story = { +export const Fallback = meta.story({ args: { - ...Default.args, + ...Default.input.args, src: 'https://avatars.githubusercontent.com/u/15406AAAAAAAAA', }, -}; +}); -export const Sizes: Story = { +export const Sizes = meta.story({ args: { - ...Default.args, + ...Default.input.args, }, render: args => ( @@ -62,11 +58,11 @@ export const Sizes: Story = { ), -}; +}); -export const Purpose: Story = { +export const Purpose = meta.story({ args: { - ...Default.args, + ...Default.input.args, }, render: args => ( @@ -93,4 +89,4 @@ export const Purpose: Story = { ), -}; +}); diff --git a/packages/ui/src/components/Box/Box.stories.tsx b/packages/ui/src/components/Box/Box.stories.tsx index 6637f45740..766a6668c7 100644 --- a/packages/ui/src/components/Box/Box.stories.tsx +++ b/packages/ui/src/components/Box/Box.stories.tsx @@ -1,3 +1,4 @@ +import preview from '../../../../../.storybook/preview'; /* * Copyright 2024 The Backstage Authors * @@ -15,11 +16,10 @@ */ import { ReactNode } from 'react'; -import type { Meta, StoryObj } from '@storybook/react-vite'; import { Box } from './Box'; import { Flex } from '../Flex'; -const meta = { +const meta = preview.meta({ title: 'Backstage UI/Box', component: Box, argTypes: { @@ -31,10 +31,7 @@ const meta = { control: false, }, }, -} satisfies Meta; - -export default meta; -type Story = StoryObj; +}); const diagonalStripePattern = (() => { const svg = ` @@ -47,7 +44,7 @@ const diagonalStripePattern = (() => { return `data:image/svg+xml,${encodeURIComponent(svg)}`; })(); -export const Default: Story = { +export const Default = meta.story({ args: { width: '64px', height: '64px', @@ -63,10 +60,10 @@ export const Default: Story = { color: '#2563eb', }, }, -}; +}); -export const Margin: Story = { - args: { ...Default.args }, +export const Margin = meta.story({ + args: { ...Default.input.args }, render: args => ( @@ -89,10 +86,10 @@ export const Margin: Story = { ), -}; +}); -export const ResponsiveMargin: Story = { - args: { ...Default.args }, +export const ResponsiveMargin = meta.story({ + args: { ...Default.input.args }, render: args => ( @@ -115,10 +112,10 @@ export const ResponsiveMargin: Story = { ), -}; +}); -export const CustomMargin: Story = { - args: { ...Default.args }, +export const CustomMargin = meta.story({ + args: { ...Default.input.args }, render: args => ( @@ -141,10 +138,10 @@ export const CustomMargin: Story = { ), -}; +}); -export const CustomResponsiveMargin: Story = { - args: { ...Default.args }, +export const CustomResponsiveMargin = meta.story({ + args: { ...Default.input.args }, render: args => ( @@ -167,10 +164,10 @@ export const CustomResponsiveMargin: Story = { ), -}; +}); -export const Padding: Story = { - args: { ...Default.args }, +export const Padding = meta.story({ + args: { ...Default.input.args }, render: args => ( @@ -193,10 +190,10 @@ export const Padding: Story = { ), -}; +}); -export const ResponsivePadding: Story = { - args: { ...Default.args }, +export const ResponsivePadding = meta.story({ + args: { ...Default.input.args }, render: args => ( ), -}; +}); -export const CustomPadding: Story = { - args: { ...Default.args }, +export const CustomPadding = meta.story({ + args: { ...Default.input.args }, render: args => ( @@ -263,10 +260,10 @@ export const CustomPadding: Story = { ), -}; +}); -export const CustomResponsivePadding: Story = { - args: { ...Default.args }, +export const CustomResponsivePadding = meta.story({ + args: { ...Default.input.args }, render: args => ( ), -}; +}); const CardDisplay = ({ children }: { children?: ReactNode }) => { return ( @@ -327,7 +324,7 @@ const CardDisplay = ({ children }: { children?: ReactNode }) => { ); }; -export const Display: Story = { +export const Display = meta.story({ render: args => ( @@ -346,4 +343,4 @@ export const Display: Story = { ), -}; +}); diff --git a/packages/ui/src/components/Button/Button.stories.tsx b/packages/ui/src/components/Button/Button.stories.tsx index 4734770aa4..141db4da96 100644 --- a/packages/ui/src/components/Button/Button.stories.tsx +++ b/packages/ui/src/components/Button/Button.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. @@ -13,15 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import type { Meta, StoryObj } from '@storybook/react-vite'; +import preview from '../../../../../.storybook/preview'; import { Button } from './Button'; import { Flex } from '../Flex'; import { Text } from '../Text'; import { RiArrowRightSLine, RiCloudLine } from '@remixicon/react'; import { useState } from 'react'; -const meta = { +const meta = preview.meta({ title: 'Backstage UI/Button', component: Button, argTypes: { @@ -34,18 +33,15 @@ const meta = { options: ['primary', 'secondary'], }, }, -} satisfies Meta; +}); -export default meta; -type Story = StoryObj; - -export const Default: Story = { +export const Default = meta.story({ args: { children: 'Button', }, -}; +}); -export const Variants: Story = { +export const Variants = meta.story({ args: { children: 'Button', }, @@ -69,9 +65,9 @@ export const Variants: Story = { ), -}; +}); -export const Sizes: Story = { +export const Sizes = meta.story({ args: { children: 'Button', }, @@ -85,9 +81,9 @@ export const Sizes: Story = { ), -}; +}); -export const WithIcons: Story = { +export const WithIcons = meta.story({ args: { children: 'Button', }, @@ -102,9 +98,9 @@ export const WithIcons: Story = { /> ), -}; +}); -export const FullWidth: Story = { +export const FullWidth = meta.story({ args: { children: 'Button', }, @@ -119,9 +115,9 @@ export const FullWidth: Story = { /> ), -}; +}); -export const Disabled: Story = { +export const Disabled = meta.story({ render: () => ( ), -}; +}); -export const Responsive: Story = { +export const Responsive = meta.story({ args: { children: 'Button', variant: { @@ -149,12 +145,12 @@ export const Responsive: Story = { sm: 'medium', }, }, -}; +}); const variants = ['primary', 'secondary'] as const; const sizes = ['small', 'medium'] as const; -export const Playground: Story = { +export const Playground = meta.story({ args: { children: 'Button', }, @@ -212,9 +208,9 @@ export const Playground: Story = { ))} ), -}; +}); -export const Loading: Story = { +export const Loading = meta.story({ render: () => { const [isLoading, setIsLoading] = useState(false); @@ -231,9 +227,9 @@ export const Loading: Story = { ); }, -}; +}); -export const LoadingVariants: Story = { +export const LoadingVariants = meta.story({ render: () => ( Primary @@ -289,4 +285,4 @@ export const LoadingVariants: Story = { ), -}; +}); diff --git a/packages/ui/src/components/ButtonIcon/ButtonIcon.stories.tsx b/packages/ui/src/components/ButtonIcon/ButtonIcon.stories.tsx index 97072c3664..be6dad7754 100644 --- a/packages/ui/src/components/ButtonIcon/ButtonIcon.stories.tsx +++ b/packages/ui/src/components/ButtonIcon/ButtonIcon.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. @@ -13,15 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import type { Meta, StoryObj } from '@storybook/react-vite'; +import preview from '../../../../../.storybook/preview'; import { ButtonIcon } from './ButtonIcon'; import { Flex } from '../Flex'; import { Text } from '../Text'; import { RiCloudLine } from '@remixicon/react'; import { useState } from 'react'; -const meta = { +const meta = preview.meta({ title: 'Backstage UI/ButtonIcon', component: ButtonIcon, argTypes: { @@ -34,16 +33,13 @@ const meta = { options: ['primary', 'secondary'], }, }, -} satisfies Meta; +}); -export default meta; -type Story = StoryObj; - -export const Default: Story = { +export const Default = meta.story({ render: () => } />, -}; +}); -export const Variants: Story = { +export const Variants = meta.story({ render: () => ( } variant="primary" /> @@ -51,18 +47,18 @@ export const Variants: Story = { } variant="tertiary" /> ), -}; +}); -export const Sizes: Story = { +export const Sizes = meta.story({ render: () => ( } size="small" /> } size="medium" /> ), -}; +}); -export const Disabled: Story = { +export const Disabled = meta.story({ render: () => ( } variant="primary" /> @@ -70,9 +66,9 @@ export const Disabled: Story = { } variant="tertiary" /> ), -}; +}); -export const Responsive: Story = { +export const Responsive = meta.story({ args: { variant: { initial: 'primary', @@ -84,9 +80,9 @@ export const Responsive: Story = { }, }, render: args => } />, -}; +}); -export const Loading: Story = { +export const Loading = meta.story({ render: () => { const [isLoading, setIsLoading] = useState(false); @@ -106,9 +102,9 @@ export const Loading: Story = { /> ); }, -}; +}); -export const LoadingVariants: Story = { +export const LoadingVariants = meta.story({ render: () => ( Primary @@ -172,4 +168,4 @@ export const LoadingVariants: Story = { ), -}; +}); diff --git a/packages/ui/src/components/ButtonIcon/ButtonIcon.tsx b/packages/ui/src/components/ButtonIcon/ButtonIcon.tsx index d3596da799..9bbeca9d5b 100644 --- a/packages/ui/src/components/ButtonIcon/ButtonIcon.tsx +++ b/packages/ui/src/components/ButtonIcon/ButtonIcon.tsx @@ -64,7 +64,6 @@ export const ButtonIcon = forwardRef( classNamesButtonIcon.content, stylesButton[classNames.content], stylesButtonIcon[classNamesButtonIcon.content], - className, )} > {icon} @@ -79,7 +78,6 @@ export const ButtonIcon = forwardRef( classNamesButtonIcon.spinner, stylesButton[classNames.spinner], stylesButtonIcon[classNamesButtonIcon.spinner], - className, )} > )} + 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 component; + return anchorElement; } // For internal links, use RouterProvider return ( - {component} + {anchorElement} ); }); 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.stories.tsx b/packages/ui/src/components/Menu/Menu.stories.tsx index 7171b3ef83..ffe184d4ae 100644 --- a/packages/ui/src/components/Menu/Menu.stories.tsx +++ b/packages/ui/src/components/Menu/Menu.stories.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { Meta, StoryObj } from '@storybook/react-vite'; +import preview from '../../../../../.storybook/preview'; import { MenuTrigger, SubmenuTrigger, @@ -38,7 +38,7 @@ import { import { MemoryRouter } from 'react-router-dom'; import { useEffect, useState } from 'react'; -const meta = { +const meta = preview.meta({ title: 'Backstage UI/Menu', component: MenuTrigger, decorators: [ @@ -48,10 +48,7 @@ const meta = { ), ], -} satisfies Meta; - -export default meta; -type Story = StoryObj; +}); const options = [ { label: 'Apple', value: 'apple' }, @@ -65,7 +62,7 @@ const options = [ { label: 'Honeydew', value: 'honeydew' }, ]; -export const Preview: Story = { +export const Preview = meta.story({ args: { children: null, }, @@ -91,11 +88,11 @@ export const Preview: Story = { ), -}; +}); -export const PreviewSubmenu: Story = { +export const PreviewSubmenu = meta.story({ args: { - ...Preview.args, + ...Preview.input.args, }, render: () => ( @@ -119,11 +116,11 @@ export const PreviewSubmenu: Story = { ), -}; +}); -export const PreviewIcons: Story = { +export const PreviewIcons = meta.story({ args: { - ...Preview.args, + ...Preview.input.args, }, render: () => ( @@ -135,11 +132,11 @@ export const PreviewIcons: Story = { ), -}; +}); -export const PreviewSections: Story = { +export const PreviewSections = meta.story({ args: { - ...Preview.args, + ...Preview.input.args, }, render: () => ( @@ -159,11 +156,11 @@ export const PreviewSections: Story = { ), -}; +}); -export const PreviewSeparators: Story = { +export const PreviewSeparators = meta.story({ args: { - ...Preview.args, + ...Preview.input.args, }, render: () => ( @@ -180,11 +177,11 @@ export const PreviewSeparators: Story = { ), -}; +}); -export const PreviewLinks: Story = { +export const PreviewLinks = meta.story({ args: { - ...Preview.args, + ...Preview.input.args, }, render: () => ( @@ -198,11 +195,11 @@ export const PreviewLinks: Story = { ), -}; +}); -export const Opened: Story = { +export const Opened = meta.story({ args: { - ...Preview.args, + ...Preview.input.args, }, render: () => ( @@ -214,11 +211,11 @@ export const Opened: Story = { ), -}; +}); -export const WithIcons: Story = { +export const WithIcons = meta.story({ args: { - ...Preview.args, + ...Preview.input.args, }, render: () => ( @@ -230,11 +227,11 @@ export const WithIcons: Story = { ), -}; +}); -export const WithSections: Story = { +export const WithSections = meta.story({ args: { - ...Preview.args, + ...Preview.input.args, }, render: () => ( @@ -254,11 +251,11 @@ export const WithSections: Story = { ), -}; +}); -export const WithSeparators: Story = { +export const WithSeparators = meta.story({ args: { - ...Preview.args, + ...Preview.input.args, }, render: () => ( @@ -275,11 +272,11 @@ export const WithSeparators: Story = { ), -}; +}); -export const WithColors: Story = { +export const WithColors = meta.story({ args: { - ...Preview.args, + ...Preview.input.args, }, render: () => ( @@ -295,11 +292,11 @@ export const WithColors: Story = { ), -}; +}); -export const WithLinks: Story = { +export const WithLinks = meta.story({ args: { - ...Preview.args, + ...Preview.input.args, }, render: () => ( @@ -313,11 +310,11 @@ export const WithLinks: Story = { ), -}; +}); -export const Submenu: Story = { +export const Submenu = meta.story({ args: { - ...Preview.args, + ...Preview.input.args, }, render: () => ( @@ -341,11 +338,11 @@ export const Submenu: Story = { ), -}; +}); -export const Virtualized: Story = { +export const Virtualized = meta.story({ args: { - ...Preview.args, + ...Preview.input.args, }, render: () => { const [pokemon, setPokemon] = useState< @@ -376,11 +373,11 @@ export const Virtualized: Story = { ); }, -}; +}); -export const VirtualizedMaxHeight: Story = { +export const VirtualizedMaxHeight = meta.story({ args: { - ...Preview.args, + ...Preview.input.args, }, render: () => { const [pokemon, setPokemon] = useState< @@ -411,9 +408,9 @@ export const VirtualizedMaxHeight: Story = { ); }, -}; +}); -export const WithScroll: Story = { +export const WithScroll = meta.story({ args: { children: null, }, @@ -446,4 +443,4 @@ export const WithScroll: Story = { ), -}; +}); diff --git a/packages/ui/src/components/Menu/MenuAutocomplete.stories.tsx b/packages/ui/src/components/Menu/MenuAutocomplete.stories.tsx index c0e1aca932..af68ec9415 100644 --- a/packages/ui/src/components/Menu/MenuAutocomplete.stories.tsx +++ b/packages/ui/src/components/Menu/MenuAutocomplete.stories.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { Meta, StoryObj } from '@storybook/react-vite'; +import preview from '../../../../../.storybook/preview'; import { MenuTrigger, SubmenuTrigger, @@ -26,7 +26,7 @@ import { Button } from '../..'; import { useState, useEffect } from 'react'; import { MemoryRouter } from 'react-router-dom'; -const meta = { +const meta = preview.meta({ title: 'Backstage UI/MenuAutocomplete', component: MenuTrigger, decorators: [ @@ -36,10 +36,7 @@ const meta = { ), ], -} satisfies Meta; - -export default meta; -type Story = StoryObj; +}); const options = [ { label: 'Apple', value: 'apple' }, @@ -53,7 +50,7 @@ const options = [ { label: 'Honeydew', value: 'honeydew' }, ]; -export const Default: Story = { +export const Default = meta.story({ args: { children: null, }, @@ -72,11 +69,11 @@ export const Default: Story = { ), -}; +}); -export const PreviewAutocompleteMenu: Story = { +export const PreviewAutocompleteMenu = meta.story({ args: { - ...Default.args, + ...Default.input.args, }, render: () => ( @@ -93,11 +90,11 @@ export const PreviewAutocompleteMenu: Story = { ), -}; +}); -export const Virtualized: Story = { +export const Virtualized = meta.story({ args: { - ...Default.args, + ...Default.input.args, }, render: () => { const [pokemon, setPokemon] = useState< @@ -132,11 +129,11 @@ export const Virtualized: Story = { ); }, -}; +}); -export const VirtualizedMaxHeight: Story = { +export const VirtualizedMaxHeight = meta.story({ args: { - ...Default.args, + ...Default.input.args, }, render: () => { const [pokemon, setPokemon] = useState< @@ -172,11 +169,11 @@ export const VirtualizedMaxHeight: Story = { ); }, -}; +}); -export const Submenu: Story = { +export const Submenu = meta.story({ args: { - ...Default.args, + ...Default.input.args, }, render: () => ( @@ -197,4 +194,4 @@ export const Submenu: Story = { ), -}; +}); diff --git a/packages/ui/src/components/Menu/MenuAutocompleteListBox.stories.tsx b/packages/ui/src/components/Menu/MenuAutocompleteListBox.stories.tsx index bf66f5f8fd..e1c22fbd9e 100644 --- a/packages/ui/src/components/Menu/MenuAutocompleteListBox.stories.tsx +++ b/packages/ui/src/components/Menu/MenuAutocompleteListBox.stories.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { Meta, StoryObj } from '@storybook/react-vite'; +import preview from '../../../../../.storybook/preview'; import { MenuTrigger, MenuAutocompleteListbox, @@ -28,7 +28,7 @@ import { useEffect, useState } from 'react'; import { Selection } from 'react-aria-components'; import { MemoryRouter } from 'react-router-dom'; -const meta = { +const meta = preview.meta({ title: 'Backstage UI/MenuAutocompleteListBox', component: MenuTrigger, decorators: [ @@ -38,10 +38,7 @@ const meta = { ), ], -} satisfies Meta; - -export default meta; -type Story = StoryObj; +}); const options = [ { label: 'Apple', value: 'apple' }, @@ -55,7 +52,7 @@ const options = [ { label: 'Honeydew', value: 'honeydew' }, ]; -export const Default: Story = { +export const Default = meta.story({ args: { children: null, }, @@ -83,11 +80,11 @@ export const Default: Story = { ); }, -}; +}); -export const PreviewListbox: Story = { +export const PreviewListbox = meta.story({ args: { - ...Default.args, + ...Default.input.args, }, render: () => { const [selected, setSelected] = useState( @@ -113,11 +110,11 @@ export const PreviewListbox: Story = { ); }, -}; +}); -export const PreviewListboxMultiple: Story = { +export const PreviewListboxMultiple = meta.story({ args: { - ...Default.args, + ...Default.input.args, }, render: () => { const [selected, setSelected] = useState( @@ -144,11 +141,11 @@ export const PreviewListboxMultiple: Story = { ); }, -}; +}); -export const Submenu: Story = { +export const Submenu = meta.story({ args: { - ...Default.args, + ...Default.input.args, }, render: () => { const [selected, setSelected] = useState( @@ -182,11 +179,11 @@ export const Submenu: Story = { ); }, -}; +}); -export const Virtualized: Story = { +export const Virtualized = meta.story({ args: { - ...Default.args, + ...Default.input.args, }, render: () => { const [pokemon, setPokemon] = useState< @@ -221,11 +218,11 @@ export const Virtualized: Story = { ); }, -}; +}); -export const VirtualizedMaxHeight: Story = { +export const VirtualizedMaxHeight = meta.story({ args: { - ...Default.args, + ...Default.input.args, }, render: () => { const [pokemon, setPokemon] = useState< @@ -261,4 +258,4 @@ export const VirtualizedMaxHeight: Story = { ); }, -}; +}); diff --git a/packages/ui/src/components/Menu/MenuListBox.stories.tsx b/packages/ui/src/components/Menu/MenuListBox.stories.tsx index 72284fb6b9..2297598d0c 100644 --- a/packages/ui/src/components/Menu/MenuListBox.stories.tsx +++ b/packages/ui/src/components/Menu/MenuListBox.stories.tsx @@ -14,14 +14,14 @@ * limitations under the License. */ -import type { Meta, StoryObj } from '@storybook/react-vite'; +import preview from '../../../../../.storybook/preview'; import { MenuTrigger, MenuListBox, MenuListBoxItem } from './index'; import { Button, Flex, Text } from '../..'; import { useEffect, useState } from 'react'; import { Selection } from 'react-aria-components'; import { MemoryRouter } from 'react-router-dom'; -const meta = { +const meta = preview.meta({ title: 'Backstage UI/MenuListBox', component: MenuTrigger, decorators: [ @@ -31,12 +31,9 @@ const meta = { ), ], -} satisfies Meta; +}); -export default meta; -type Story = StoryObj; - -export const Default: Story = { +export const Default = meta.story({ args: { children: null, }, @@ -50,11 +47,11 @@ export const Default: Story = { ), -}; +}); -export const Controlled: Story = { +export const Controlled = meta.story({ args: { - ...Default.args, + ...Default.input.args, }, render: () => { const [selected, setSelected] = useState(new Set(['paul'])); @@ -86,11 +83,11 @@ export const Controlled: Story = { ); }, -}; +}); -export const Virtualized: Story = { +export const Virtualized = meta.story({ args: { - ...Default.args, + ...Default.input.args, }, render: () => { const [pokemon, setPokemon] = useState< @@ -121,11 +118,11 @@ export const Virtualized: Story = { ); }, -}; +}); -export const VirtualizedMaxHeight: Story = { +export const VirtualizedMaxHeight = meta.story({ args: { - ...Default.args, + ...Default.input.args, }, render: () => { const [pokemon, setPokemon] = useState< @@ -156,4 +153,4 @@ export const VirtualizedMaxHeight: Story = { ); }, -}; +}); diff --git a/packages/ui/src/components/PasswordField/PasswordField.stories.tsx b/packages/ui/src/components/PasswordField/PasswordField.stories.tsx index 1867e5b7bf..5f6a74d992 100644 --- a/packages/ui/src/components/PasswordField/PasswordField.stories.tsx +++ b/packages/ui/src/components/PasswordField/PasswordField.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. @@ -13,15 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import type { Meta, StoryObj } from '@storybook/react-vite'; +import preview from '../../../../../.storybook/preview'; import { PasswordField } from './PasswordField'; import { Form } from 'react-aria-components'; import { Flex } from '../Flex'; import { FieldLabel } from '../FieldLabel'; import { RiSparklingLine } from '@remixicon/react'; -const meta = { +const meta = preview.meta({ title: 'Backstage UI/PasswordField', component: PasswordField, argTypes: { @@ -32,12 +31,9 @@ const meta = { control: 'object', }, }, -} satisfies Meta; +}); -export default meta; -type Story = StoryObj; - -export const Default: Story = { +export const Default = meta.story({ args: { name: 'secret', placeholder: 'Enter a secret', @@ -45,11 +41,11 @@ export const Default: Story = { maxWidth: '300px', }, }, -}; +}); -export const Sizes: Story = { +export const Sizes = meta.story({ args: { - ...Default.args, + ...Default.input.args, }, render: args => ( @@ -57,79 +53,79 @@ export const Sizes: Story = { } /> ), -}; +}); -export const DefaultValue: Story = { +export const DefaultValue = meta.story({ args: { - ...Default.args, + ...Default.input.args, defaultValue: 'https://example.com', }, -}; +}); -export const WithLabel: Story = { +export const WithLabel = meta.story({ args: { - ...Default.args, + ...Default.input.args, label: 'Label', }, -}; +}); -export const WithDescription: Story = { +export const WithDescription = meta.story({ args: { - ...WithLabel.args, + ...WithLabel.input.args, description: 'Description', }, -}; +}); -export const Required: Story = { +export const Required = meta.story({ args: { - ...WithLabel.args, + ...WithLabel.input.args, isRequired: true, }, -}; +}); -export const Disabled: Story = { +export const Disabled = meta.story({ args: { - ...Default.args, + ...Default.input.args, isDisabled: true, }, -}; +}); -export const WithIcon: Story = { +export const WithIcon = meta.story({ args: { - ...Default.args, + ...Default.input.args, }, render: args => ( } /> ), -}; +}); -export const DisabledWithIcon: Story = { +export const DisabledWithIcon = meta.story({ args: { - ...WithIcon.args, + ...WithIcon.input.args, isDisabled: true, }, - render: WithIcon.render, -}; + render: WithIcon.input.render, +}); -export const ShowError: Story = { +export const ShowError = meta.story({ args: { - ...WithLabel.args, + ...WithLabel.input.args, }, render: args => (
), -}; +}); -export const Validation: Story = { +export const Validation = meta.story({ args: { - ...WithLabel.args, + ...WithLabel.input.args, validate: value => (value === 'admin' ? 'Nice try!' : null), }, -}; +}); -export const CustomField: Story = { +export const CustomField = meta.story({ render: () => ( <> ), -}; +}); diff --git a/packages/ui/src/components/RadioGroup/RadioGroup.stories.tsx b/packages/ui/src/components/RadioGroup/RadioGroup.stories.tsx index df7a826c50..4f3465dade 100644 --- a/packages/ui/src/components/RadioGroup/RadioGroup.stories.tsx +++ b/packages/ui/src/components/RadioGroup/RadioGroup.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. @@ -13,19 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import type { Meta, StoryObj } from '@storybook/react-vite'; +import preview from '../../../../../.storybook/preview'; import { RadioGroup, Radio } from './RadioGroup'; -const meta = { +const meta = preview.meta({ title: 'Backstage UI/RadioGroup', component: RadioGroup, -} satisfies Meta; +}); -export default meta; -type Story = StoryObj; - -export const Default: Story = { +export const Default = meta.story({ args: { label: 'What is your favorite pokemon?', }, @@ -36,11 +32,11 @@ export const Default: Story = { Squirtle ), -}; +}); -export const Horizontal: Story = { +export const Horizontal = meta.story({ args: { - ...Default.args, + ...Default.input.args, orientation: 'horizontal', }, render: args => ( @@ -50,11 +46,11 @@ export const Horizontal: Story = { Squirtle ), -}; +}); -export const Disabled: Story = { +export const Disabled = meta.story({ args: { - ...Default.args, + ...Default.input.args, isDisabled: true, }, render: args => ( @@ -64,11 +60,11 @@ export const Disabled: Story = { Squirtle ), -}; +}); -export const DisabledSingle: Story = { +export const DisabledSingle = meta.story({ args: { - ...Default.args, + ...Default.input.args, }, render: args => ( @@ -79,11 +75,11 @@ export const DisabledSingle: Story = { Squirtle ), -}; +}); -export const DisabledAndSelected: Story = { +export const DisabledAndSelected = meta.story({ args: { - ...Default.args, + ...Default.input.args, value: 'charmander', }, render: args => ( @@ -95,11 +91,11 @@ export const DisabledAndSelected: Story = { Squirtle ), -}; +}); -export const Invalid: Story = { +export const Invalid = meta.story({ args: { - ...Default.args, + ...Default.input.args, name: 'pokemon', isInvalid: true, }, @@ -112,11 +108,11 @@ export const Invalid: Story = { Squirtle ), -}; +}); -export const Validation: Story = { +export const Validation = meta.story({ args: { - ...Default.args, + ...Default.input.args, name: 'pokemon', defaultValue: 'charmander', validationBehavior: 'aria', @@ -129,11 +125,11 @@ export const Validation: Story = { Squirtle ), -}; +}); -export const ReadOnly: Story = { +export const ReadOnly = meta.story({ args: { - ...Default.args, + ...Default.input.args, isReadOnly: true, defaultValue: 'charmander', }, @@ -144,4 +140,4 @@ export const ReadOnly: Story = { Squirtle ), -}; +}); diff --git a/packages/ui/src/components/SearchField/SearchField.module.css b/packages/ui/src/components/SearchField/SearchField.module.css index e99c212b29..765527c8c3 100644 --- a/packages/ui/src/components/SearchField/SearchField.module.css +++ b/packages/ui/src/components/SearchField/SearchField.module.css @@ -40,24 +40,25 @@ } &[data-startCollapsed='true'] { - transition: flex-basis 0.3s ease-in-out; + transition: flex-basis 0.3s ease-in-out, width 0.3s ease-in-out, + max-width 0.3s ease-in-out; padding: 0; flex: 0 1 auto; &[data-collapsed='true'] { - flex-basis: 200px; - } - - &[data-collapsed='false'] { cursor: pointer; &[data-size='medium'] { flex-basis: 2.5rem; + width: 2.5rem; + max-width: 2.5rem; height: 2.5rem; } &[data-size='small'] { flex-basis: 2rem; + width: 2rem; + max-width: 2rem; height: 2rem; } @@ -79,6 +80,12 @@ } } } + + &[data-collapsed='false'] { + flex-basis: 200px; + width: 200px; + max-width: 200px; + } } } diff --git a/packages/ui/src/components/SearchField/SearchField.stories.tsx b/packages/ui/src/components/SearchField/SearchField.stories.tsx index 81bc090835..2e07e742ad 100644 --- a/packages/ui/src/components/SearchField/SearchField.stories.tsx +++ b/packages/ui/src/components/SearchField/SearchField.stories.tsx @@ -14,7 +14,8 @@ * limitations under the License. */ -import type { Meta, StoryObj } from '@storybook/react-vite'; +import preview from '../../../../../.storybook/preview'; +import { useState } from 'react'; import { SearchField } from './SearchField'; import { Form } from 'react-aria-components'; import { Flex } from '../Flex'; @@ -25,7 +26,7 @@ import { Button } from '../Button'; import { Header } from '../Header'; import { MemoryRouter } from 'react-router-dom'; -const meta = { +const meta = preview.meta({ title: 'Backstage UI/SearchField', component: SearchField, argTypes: { @@ -39,12 +40,9 @@ const meta = { control: 'text', }, }, -} satisfies Meta; +}); -export default meta; -type Story = StoryObj; - -export const Default: Story = { +export const Default = meta.story({ args: { name: 'url', style: { @@ -52,11 +50,11 @@ export const Default: Story = { }, 'aria-label': 'Search', }, -}; +}); -export const Sizes: Story = { +export const Sizes = meta.story({ args: { - ...Default.args, + ...Default.input.args, }, render: args => ( @@ -64,46 +62,46 @@ export const Sizes: Story = { ), -}; +}); -export const DefaultValue: Story = { +export const DefaultValue = meta.story({ args: { - ...Default.args, + ...Default.input.args, defaultValue: 'https://example.com', }, -}; +}); -export const WithLabel: Story = { +export const WithLabel = meta.story({ args: { - ...Default.args, + ...Default.input.args, label: 'Label', }, -}; +}); -export const WithDescription: Story = { +export const WithDescription = meta.story({ args: { - ...WithLabel.args, + ...WithLabel.input.args, description: 'Description', }, -}; +}); -export const Required: Story = { +export const Required = meta.story({ args: { - ...WithLabel.args, + ...WithLabel.input.args, isRequired: true, }, -}; +}); -export const Disabled: Story = { +export const Disabled = meta.story({ args: { - ...Default.args, + ...Default.input.args, isDisabled: true, }, -}; +}); -export const WithIcon: Story = { +export const WithIcon = meta.story({ args: { - ...Default.args, + ...Default.input.args, }, render: args => ( } /> ), -}; +}); -export const DisabledWithIcon: Story = { +export const DisabledWithIcon = meta.story({ args: { - ...WithIcon.args, + ...WithIcon.input.args, isDisabled: true, }, -}; +}); -export const ShowError: Story = { +export const ShowError = meta.story({ args: { - ...WithLabel.args, + ...WithLabel.input.args, }, render: args => (
), -}; +}); -export const Validation: Story = { +export const Validation = meta.story({ args: { - ...WithLabel.args, + ...WithLabel.input.args, validate: value => (value === 'admin' ? 'Nice try!' : null), }, -}; +}); -export const CustomField: Story = { +export const CustomField = meta.story({ render: () => ( <> ), -}; +}); -export const StartCollapsed: Story = { +export const StartCollapsed = meta.story({ args: { - ...Default.args, + ...Default.input.args, startCollapsed: true, }, render: args => ( - + + + + + - ), -}; +}); -export const InHeader: Story = { +export const StartCollapsedWithValue = meta.story({ + args: { + ...StartCollapsed.input.args, + defaultValue: 'https://example.com', + }, + + render: args => , +}); + +export const InHeader = meta.story({ decorators: [ Story => ( @@ -204,11 +214,11 @@ export const InHeader: Story = { /> ), -}; +}); -export const StartCollapsedInHeader: Story = { +export const StartCollapsedInHeader = meta.story({ args: { - ...StartCollapsed.args, + ...StartCollapsed.input.args, }, decorators: [ Story => ( @@ -241,11 +251,11 @@ export const StartCollapsedInHeader: Story = { /> ), -}; +}); -export const StartCollapsedWithButtons: Story = { +export const StartCollapsedWithButtons = meta.story({ args: { - ...StartCollapsed.args, + ...StartCollapsed.input.args, }, render: args => ( @@ -271,11 +281,11 @@ export const StartCollapsedWithButtons: Story = { ), -}; +}); -export const StartCollapsedWithOnChange: Story = { +export const StartCollapsedWithOnChange = meta.story({ args: { - ...StartCollapsed.args, + ...StartCollapsed.input.args, }, render: args => { const handleChange = (value: string) => { @@ -292,4 +302,42 @@ export const StartCollapsedWithOnChange: Story = { ); }, -}; +}); + +export const StartCollapsedControlledEmpty = meta.story({ + args: { + ...StartCollapsed.input.args, + }, + render: function Render(args) { + const [value, setValue] = useState(''); + + return ( + + + + ); + }, +}); + +export const StartCollapsedControlledWithValue = meta.story({ + args: { + ...StartCollapsed.input.args, + }, + render: function Render(args) { + const [value, setValue] = useState('Component'); + + return ( + + + + ); + }, +}); diff --git a/packages/ui/src/components/SearchField/SearchField.tsx b/packages/ui/src/components/SearchField/SearchField.tsx index 775b7accc6..eea749e46f 100644 --- a/packages/ui/src/components/SearchField/SearchField.tsx +++ b/packages/ui/src/components/SearchField/SearchField.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { forwardRef, useEffect, useState } from 'react'; +import { forwardRef, useEffect, useState, useRef } from 'react'; import { Input, SearchField as AriaSearchField, @@ -39,9 +39,6 @@ export const SearchField = forwardRef( 'aria-labelledby': ariaLabelledBy, } = props; - const [isCollapsed, setIsCollapsed] = useState(false); - const [shouldCollapse, setShouldCollapse] = useState(true); - useEffect(() => { if (!label && !ariaLabel && !ariaLabelledBy) { console.warn( @@ -71,30 +68,29 @@ export const SearchField = forwardRef( ...rest } = cleanedProps; + const [isFocused, setIsFocused] = useState(false); + const inputRef = useRef(null); + // If a secondary label is provided, use it. Otherwise, use 'Required' if the field is required. const secondaryLabelText = secondaryLabel || (isRequired ? 'Required' : null); - const handleClick = (isFocused: boolean) => { + const handleFocusChange = (isFocused: boolean) => { props.onFocusChange?.(isFocused); - if (shouldCollapse) { - if (isFocused) { - setIsCollapsed(true); - } else { - setIsCollapsed(false); - } - } + setIsFocused(isFocused); }; - const handleChange = (value: string) => { - props.onChange?.(value); - if (value.length > 0) { - setShouldCollapse(false); - } else { - setShouldCollapse(true); - } + const handleContainerClick = () => { + inputRef.current?.focus(); }; + const hasInputRef = !!inputRef.current; + const hasValue = !!inputRef.current?.value; + + const isCollapsed = hasInputRef + ? startCollapsed && !hasValue && !isFocused + : startCollapsed && !rest.value && !rest.defaultValue && !isFocused; + return ( ( aria-label={ariaLabel} aria-labelledby={ariaLabelledBy} data-collapsed={isCollapsed} - onFocusChange={handleClick} - onChange={handleChange} style={style} {...rest} + onFocusChange={handleFocusChange} ref={ref} > ( styles[classNames.inputWrapper], )} data-size={dataAttributes['data-size']} + onClick={handleContainerClick} > {icon !== false && (
(
)} ; - -export default meta; -type Story = StoryObj; +}); const fontOptions = [ { value: 'sans', label: 'Sans-serif' }, @@ -66,23 +64,23 @@ const skills = [ { value: 'swift', label: 'Swift' }, ]; -export const Default: Story = { +export const Default = meta.story({ args: { options: fontOptions, name: 'font', }, -}; +}); -export const Searchable: Story = { +export const Searchable = meta.story({ args: { label: 'Country', searchable: true, searchPlaceholder: 'Search countries...', options: countries, }, -}; +}); -export const MultipleSelection: Story = { +export const MultipleSelection = meta.story({ args: { label: 'Select multiple options', selectionMode: 'multiple', @@ -93,9 +91,9 @@ export const MultipleSelection: Story = { { value: 'option4', label: 'Option 4' }, ], }, -}; +}); -export const SearchableMultiple: Story = { +export const SearchableMultiple = meta.story({ args: { label: 'Skills', searchable: true, @@ -103,9 +101,9 @@ export const SearchableMultiple: Story = { searchPlaceholder: 'Filter skills...', options: skills, }, -}; +}); -export const Preview: Story = { +export const Preview = meta.story({ args: { label: 'Font Family', options: fontOptions, @@ -113,40 +111,40 @@ export const Preview: Story = { name: 'font', style: { maxWidth: 260 }, }, -}; +}); -export const WithLabel: Story = { +export const WithLabel = meta.story({ args: { - ...Default.args, + ...Default.input.args, label: 'Font Family', }, -}; +}); -export const WithFullWidth: Story = { +export const WithFullWidth = meta.story({ args: { - ...Default.args, + ...Default.input.args, label: 'Font Family', style: { width: '100%' }, }, -}; +}); -export const WithLabelAndDescription: Story = { +export const WithLabelAndDescription = meta.story({ args: { - ...WithLabel.args, + ...WithLabel.input.args, description: 'Choose a font family for your document', }, -}; +}); -export const WithIcon: Story = { +export const WithIcon = meta.story({ args: { - ...WithLabel.args, + ...WithLabel.input.args, }, render: args => } /> ), -}; +}); -export const Required: Story = { +export const Required = meta.story({ args: { - ...Preview.args, + ...Preview.input.args, isRequired: true, }, -}; +}); -export const Disabled: Story = { +export const Disabled = meta.story({ args: { - ...Preview.args, + ...Preview.input.args, isDisabled: true, }, -}; +}); -export const DisabledOption: Story = { +export const DisabledOption = meta.story({ args: { - ...Preview.args, + ...Preview.input.args, disabledKeys: ['cursive', 'serif'], }, -}; +}); -export const NoOptions: Story = { +export const NoOptions = meta.story({ args: { - ...Preview.args, + ...Preview.input.args, options: undefined, }, -}; +}); -export const WithValue: Story = { +export const WithValue = meta.story({ args: { - ...Preview.args, + ...Preview.input.args, selectedKey: 'mono', defaultSelectedKey: 'serif', }, -}; +}); -export const WithDefaultValue: Story = { +export const WithDefaultValue = meta.story({ args: { - ...Preview.args, + ...Preview.input.args, defaultSelectedKey: 'serif', options: fontOptions, name: 'font', }, -}; +}); const generateOptions = (count = 100) => { const firstWords = [ @@ -323,17 +321,17 @@ const generateOptions = (count = 100) => { })); }; -export const WithManyOptions: Story = { +export const WithManyOptions = meta.story({ args: { label: 'Font Family', options: generateOptions(), name: 'font', }, -}; +}); -export const WithError: Story = { +export const WithError = meta.story({ args: { - ...WithLabel.args, + ...WithLabel.input.args, name: 'font', }, render: args => ( @@ -341,9 +339,9 @@ export const WithError: Story = {
@@ -65,27 +68,27 @@ export const TableOnly: Story = { {data1.map(item => ( - } description={item.description} /> - - - + + ))}
); }, -}; +}); -export const WithPaginationUncontrolled: Story = { +export const WithPaginationUncontrolled = meta.story({ render: () => { const { data, paginationProps } = useTable({ data: data1 }); @@ -101,14 +104,14 @@ export const WithPaginationUncontrolled: Story = { {data?.map(item => ( - } description={item.description} /> - - - + + + ))} @@ -117,9 +120,9 @@ export const WithPaginationUncontrolled: Story = { ); }, -}; +}); -export const WithPaginationControlled: Story = { +export const WithPaginationControlled = meta.story({ render: () => { const [offset, setOffset] = useState(0); const [pageSize, setPageSize] = useState(5); @@ -148,14 +151,14 @@ export const WithPaginationControlled: Story = { {data?.map(item => ( - - - - + + + ))} @@ -167,9 +170,9 @@ export const WithPaginationControlled: Story = { ); }, -}; +}); -export const Sorting: Story = { +export const Sorting = meta.story({ render: () => { return ( @@ -184,27 +187,27 @@ export const Sorting: Story = { {data1.map(item => ( - } description={item.description} /> - - - + + ))}
); }, -}; +}); -export const TableRockBand: Story = { +export const TableRockBand = meta.story({ render: () => { const { data, paginationProps } = useTable({ data: data4, @@ -225,14 +228,14 @@ export const TableRockBand: Story = { {data?.map(item => ( - - - - + + + ))} @@ -241,9 +244,9 @@ export const TableRockBand: Story = { ); }, -}; +}); -export const RowClick: Story = { +export const RowClick = meta.story({ render: () => { const { data, paginationProps } = useTable({ data: data4, @@ -264,14 +267,14 @@ export const RowClick: Story = { {data?.map(item => ( alert('Row clicked')}> - - - - + + + ))} @@ -280,9 +283,9 @@ export const RowClick: Story = { ); }, -}; +}); -export const RowLink: Story = { +export const RowLink = meta.story({ render: () => { const { data, paginationProps } = useTable({ data: data4, @@ -303,14 +306,14 @@ export const RowLink: Story = { {data?.map(item => ( - - - - + + + ))} @@ -319,9 +322,40 @@ export const RowLink: Story = { ); }, -}; +}); -export const CellText: Story = { +export const CellComponent = meta.story({ + name: 'Cell', + render: () => { + return ( + + + Name + + + + Hello world + + + + This is a very long text that demonstrates how the Cell component + handles lengthy content. It should wrap appropriately and maintain + proper styling even when the text extends beyond the normal cell + width. This helps ensure that the table remains readable and + visually consistent regardless of the content length. + + + + Hello world + + +
+ ); + }, +}); + +export const CellTextComponent = meta.story({ + name: 'CellText', render: () => { return ( @@ -331,10 +365,11 @@ export const CellText: Story = { {data2.map(item => ( - ))} @@ -342,9 +377,10 @@ export const CellText: Story = {
); }, -}; +}); -export const CellProfile: Story = { +export const CellProfileComponent = meta.story({ + name: 'CellProfile', render: () => { return ( @@ -354,7 +390,7 @@ export const CellProfile: Story = { {data3.map(item => ( - ); }, -}; +}); + +export const SelectionSingleToggle = meta.story({ + render: () => { + const [selectedKeys, setSelectedKeys] = useState(new Set([])); + + return ( +
+ + Name + Owner + Type + + + + + + + + + + + + + + + + + + +
+ ); + }, +}); + +export const SelectionMultiToggle = meta.story({ + render: () => { + const [selectedKeys, setSelectedKeys] = useState(new Set([])); + + return ( + + + Name + Owner + Type + + + + + + + + + + + + + + + + + + +
+ ); + }, +}); + +export const SelectionSingleReplace = meta.story({ + render: () => { + const [selectedKeys, setSelectedKeys] = useState(new Set([])); + + return ( + + + Name + Owner + Type + + + + + + + + + + + + + + + + + + +
+ ); + }, +}); + +export const SelectionMultiReplace = meta.story({ + render: () => { + const [selectedKeys, setSelectedKeys] = useState(new Set([])); + + return ( + + + Name + Owner + Type + + + + + + + + + + + + + + + + + + +
+ ); + }, +}); + +export const SelectionToggleWithActions = meta.story({ + render: () => { + const [selectedKeys, setSelectedKeys] = useState(new Set([])); + + return ( + alert(`Opening ${key}`)} + > + + Name + Owner + Type + + + + + + + + + + + + + + + + + + +
+ ); + }, +}); + +export const SelectionReplaceWithActions = meta.story({ + render: () => { + const [selectedKeys, setSelectedKeys] = useState(new Set([])); + + return ( + alert(`Opening ${key}`)} + > + + Name + Owner + Type + + + + + + + + + + + + + + + + + + +
+ ); + }, +}); + +export const SelectionToggleWithLinks = meta.story({ + render: () => { + const [selectedKeys, setSelectedKeys] = useState(new Set([])); + + return ( + + + Name + Owner + Type + + + + + + + + + + + + + + + + + + +
+ ); + }, +}); + +export const SelectionReplaceWithLinks = meta.story({ + render: () => { + const [selectedKeys, setSelectedKeys] = useState(new Set([])); + + return ( + + + Name + Owner + Type + + + + + + + + + + + + + + + + + + +
+ ); + }, +}); + +export const SelectionWithDisabledRows = meta.story({ + render: () => { + const [selectedKeys, setSelectedKeys] = useState(new Set([])); + + return ( + + + Name + Owner + Type + + + + + + + + + + + + + + + + + + +
+ ); + }, +}); + +export const SelectionWithPagination = meta.story({ + render: () => { + const [selectedKeys, setSelectedKeys] = useState(new Set([])); + + const { data, paginationProps } = useTable({ + data: data1, + pagination: { + defaultPageSize: 5, + }, + }); + + return ( + <> + + + Name + Owner + Type + + + {data?.map(item => ( + + + + + + ))} + +
+ + + ); + }, +}); + +export const SelectionModePlayground = meta.story({ + render: () => { + const [selectionMode, setSelectionMode] = useState<'single' | 'multiple'>( + 'multiple', + ); + const [selectedKeys, setSelectedKeys] = useState(new Set([])); + + return ( + + + + Name + Owner + Type + + + + + + + + + + + + + + + + + + +
+
+ + Selection mode: + + { + setSelectionMode(value as 'single' | 'multiple'); + setSelectedKeys(new Set([])); + }} + > + single + multiple + +
+
+ ); + }, +}); + +export const SelectionBehaviorPlayground = meta.story({ + render: () => { + const [selectionBehavior, setSelectionBehavior] = useState< + 'toggle' | 'replace' + >('toggle'); + const [selectedKeys, setSelectedKeys] = useState(new Set([])); + + return ( + + + + Name + Owner + Type + + + + + + + + + + + + + + + + + + +
+
+ + Selection behavior: + + { + setSelectionBehavior(value as 'toggle' | 'replace'); + setSelectedKeys(new Set([])); + }} + > + toggle + replace + +
+
+ ); + }, +}); diff --git a/packages/ui/src/components/Table/components/Cell.tsx b/packages/ui/src/components/Table/components/Cell.tsx index 65be8482a7..809b224a07 100644 --- a/packages/ui/src/components/Table/components/Cell.tsx +++ b/packages/ui/src/components/Table/components/Cell.tsx @@ -15,8 +15,6 @@ */ import clsx from 'clsx'; -import { Text } from '../../Text'; -import { Link } from '../../Link'; import { Cell as ReactAriaCell } from 'react-aria-components'; import type { CellProps } from '../types'; import { useStyles } from '../../../hooks/useStyles'; @@ -29,49 +27,14 @@ const Cell = (props: CellProps) => { color: 'primary' as const, ...props, }); - const { className, title, description, color, leadingIcon, href, ...rest } = - cleanedProps; + const { className, children, ...rest } = cleanedProps; return ( -
- {leadingIcon && ( -
- {leadingIcon} -
- )} -
- {href ? ( - - {title} - - ) : ( - - {title} - - )} - {description && ( - - {description} - - )} -
-
+ {children}
); }; diff --git a/packages/ui/src/components/Table/components/CellText.tsx b/packages/ui/src/components/Table/components/CellText.tsx new file mode 100644 index 0000000000..8855c92e80 --- /dev/null +++ b/packages/ui/src/components/Table/components/CellText.tsx @@ -0,0 +1,98 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import clsx from 'clsx'; +import { Text } from '../../Text'; +import { Link } from '../../Link'; +import { Cell as ReactAriaCell } from 'react-aria-components'; +import type { CellTextProps } from '../types'; +import { useStyles } from '../../../hooks/useStyles'; +import { TableDefinition } from '../definition'; +import styles from '../Table.module.css'; + +/** @public */ +const CellText = (props: CellTextProps) => { + const { classNames, cleanedProps } = useStyles(TableDefinition, { + color: 'primary' as const, + ...props, + }); + const { className, title, description, color, leadingIcon, href, ...rest } = + cleanedProps; + + return ( + +
+ {leadingIcon && ( +
+ {leadingIcon} +
+ )} +
+ {href ? ( + + {title} + + ) : ( + + {title} + + )} + {description && ( + + {description} + + )} +
+
+
+ ); +}; + +CellText.displayName = 'CellText'; + +export { CellText }; diff --git a/packages/ui/src/components/Table/components/Column.tsx b/packages/ui/src/components/Table/components/Column.tsx index f534da565a..9076ec8557 100644 --- a/packages/ui/src/components/Table/components/Column.tsx +++ b/packages/ui/src/components/Table/components/Column.tsx @@ -25,11 +25,11 @@ import { RiArrowUpLine, RiArrowDownLine } from '@remixicon/react'; /** @public */ export const Column = (props: ColumnProps) => { const { classNames, cleanedProps } = useStyles(TableDefinition, props); - const { children, ...rest } = cleanedProps; + const { className, children, ...rest } = cleanedProps; return ( {({ allowsSorting, sortDirection }) => ( @@ -48,10 +48,10 @@ export const Column = (props: ColumnProps) => { styles[classNames.headSortButton], )} > - {sortDirection === 'ascending' ? ( - - ) : ( + {sortDirection === 'descending' ? ( + ) : ( + )} )} diff --git a/packages/ui/src/components/Table/components/Row.tsx b/packages/ui/src/components/Table/components/Row.tsx index 106f97baff..e08649c63b 100644 --- a/packages/ui/src/components/Table/components/Row.tsx +++ b/packages/ui/src/components/Table/components/Row.tsx @@ -18,11 +18,11 @@ import { Row as ReactAriaRow, RowProps, useTableOptions, - Cell, + Cell as ReactAriaCell, Collection, - Checkbox, RouterProvider, } from 'react-aria-components'; +import { Checkbox } from '../../Checkbox'; import { useStyles } from '../../../hooks/useStyles'; import { TableDefinition } from '../definition'; import { useNavigate } from 'react-router-dom'; @@ -30,6 +30,7 @@ import { useHref } from 'react-router-dom'; import { isExternalLink } from '../../../utils/isExternalLink'; import styles from '../Table.module.css'; import clsx from 'clsx'; +import { Flex } from '../../Flex'; /** @public */ export function Row(props: RowProps) { @@ -38,20 +39,29 @@ export function Row(props: RowProps) { const navigate = useNavigate(); const isExternal = isExternalLink(href); - let { selectionBehavior } = useTableOptions(); + let { selectionBehavior, selectionMode } = useTableOptions(); const content = ( <> - {selectionBehavior === 'toggle' && ( - - - + {selectionBehavior === 'toggle' && selectionMode === 'multiple' && ( + + + + <> + + + )} {children} ); - if (isExternal) { + if (!href || isExternal) { return ( (props: TableHeaderProps) => { - let { selectionBehavior, selectionMode, allowsDragging } = useTableOptions(); + let { selectionBehavior, selectionMode } = useTableOptions(); const { classNames, cleanedProps } = useStyles(TableDefinition, props); const { columns, children, ...rest } = cleanedProps; @@ -38,11 +40,21 @@ export const TableHeader = (props: TableHeaderProps) => { className={clsx(classNames.header, styles[classNames.header])} {...rest} > - {/* Add extra columns for drag and drop and selection. */} - {allowsDragging && } - {selectionBehavior === 'toggle' && ( - - {selectionMode === 'multiple' && } + {selectionBehavior === 'toggle' && selectionMode === 'multiple' && ( + + + + <> + + )} {children} diff --git a/packages/ui/src/components/Table/definition.ts b/packages/ui/src/components/Table/definition.ts index 8ec9dfb1f0..313604902c 100644 --- a/packages/ui/src/components/Table/definition.ts +++ b/packages/ui/src/components/Table/definition.ts @@ -39,5 +39,7 @@ export const TableDefinition = { cellProfileAvatarFallback: 'bui-TableCellProfileAvatarFallback', cellProfileName: 'bui-TableCellProfileName', cellProfileLink: 'bui-TableCellProfileLink', + headSelection: 'bui-TableHeadSelection', + cellSelection: 'bui-TableCellSelection', }, } as const satisfies ComponentDefinition; diff --git a/packages/ui/src/components/Table/index.ts b/packages/ui/src/components/Table/index.ts index 8d6603ee00..42ba6ff9c9 100644 --- a/packages/ui/src/components/Table/index.ts +++ b/packages/ui/src/components/Table/index.ts @@ -20,10 +20,16 @@ export { TableBody } from './components/TableBody'; export { Column } from './components/Column'; export { Row } from './components/Row'; export { Cell } from './components/Cell'; +export { CellText } from './components/CellText'; export { CellProfile } from './components/CellProfile'; export { useTable } from './hooks/useTable'; -export type { CellProps, CellProfileProps, ColumnProps } from './types'; +export type { + CellProps, + CellTextProps, + CellProfileProps, + ColumnProps, +} from './types'; export type { UseTableConfig, UseTableResult, diff --git a/packages/ui/src/components/Table/mocked-data2.ts b/packages/ui/src/components/Table/mocked-data2.ts index 30ca36a689..0aff7bcd98 100644 --- a/packages/ui/src/components/Table/mocked-data2.ts +++ b/packages/ui/src/components/Table/mocked-data2.ts @@ -21,6 +21,7 @@ export interface DataProps { name: string; description?: string; icon?: React.ReactNode; + href?: string; } export const data: DataProps[] = [ @@ -43,4 +44,44 @@ export const data: DataProps[] = [ 'A comprehensive service handling user authentication and role-based access control across all applications.', icon: createElement(RiCactusLine), }, + { + icon: createElement(RiCactusLine), + name: 'Cell with extremely long title that demonstrates text wrapping behavior and how the component handles content that extends well beyond typical cell widths in table layouts', + description: + 'This is a comprehensive and detailed description that contains a substantial amount of text to demonstrate how the CellText component handles lengthy content. It includes multiple sentences and covers various aspects of the service or component being described. The text should wrap appropriately within the cell boundaries while maintaining readability and proper spacing. This example helps ensure that the table remains visually consistent and user-friendly even when dealing with extensive content that might otherwise break the layout or cause accessibility issues. The component should gracefully handle such scenarios by providing appropriate text wrapping, maintaining proper line heights, and ensuring that all content remains accessible and readable regardless of its length.', + }, + { + name: 'Cell with title and URL', + href: '/catalog/default/service/auth-service', + }, + { + name: 'Cell with title, description and URL', + description: + 'A comprehensive service handling user authentication and role-based access control across all applications.', + href: '/catalog/default/service/auth-service', + }, + { + name: 'Cell with title, icon and URL', + icon: createElement(RiCactusLine), + href: '/catalog/default/service/auth-service', + }, + { + name: 'Cell with title, description, icon and URL', + description: + 'A comprehensive service handling user authentication and role-based access control across all applications.', + icon: createElement(RiCactusLine), + href: '/catalog/default/service/auth-service', + }, + { + icon: createElement(RiCactusLine), + name: 'Cell with extremely long title that demonstrates text wrapping behavior and how the component handles content that extends well beyond typical cell widths in table layouts', + description: + 'This is a comprehensive and detailed description that contains a substantial amount of text to demonstrate how the CellText component handles lengthy content. It includes multiple sentences and covers various aspects of the service or component being described. The text should wrap appropriately within the cell boundaries while maintaining readability and proper spacing. This example helps ensure that the table remains visually consistent and user-friendly even when dealing with extensive content that might otherwise break the layout or cause accessibility issues. The component should gracefully handle such scenarios by providing appropriate text wrapping, maintaining proper line heights, and ensuring that all content remains accessible and readable regardless of its length.', + href: '/catalog/default/service/auth-service', + }, + { + name: 'External link example', + description: 'This is an external link that opens in a new tab', + href: 'https://backstage.io', + }, ]; diff --git a/packages/ui/src/components/Table/types.ts b/packages/ui/src/components/Table/types.ts index be9e860a0c..1ac477e3ff 100644 --- a/packages/ui/src/components/Table/types.ts +++ b/packages/ui/src/components/Table/types.ts @@ -21,7 +21,10 @@ import { import type { TextColors } from '../../types'; /** @public */ -export interface CellProps extends ReactAriaCellProps { +export interface CellProps extends ReactAriaCellProps {} + +/** @public */ +export interface CellTextProps extends ReactAriaCellProps { title: string; description?: string; color?: TextColors; diff --git a/packages/ui/src/components/TablePagination/TablePagination.stories.tsx b/packages/ui/src/components/TablePagination/TablePagination.stories.tsx index 4550d00abb..a7d949a31d 100644 --- a/packages/ui/src/components/TablePagination/TablePagination.stories.tsx +++ b/packages/ui/src/components/TablePagination/TablePagination.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. @@ -13,13 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -// TODO: Bring useArgs() back when we update Storybook to 9 -// import { useArgs } from 'storybook/preview-api'; -import type { Meta, StoryObj } from '@storybook/react-vite'; +import preview from '../../../../../.storybook/preview'; import { TablePagination } from './TablePagination'; -const meta = { +const meta = preview.meta({ title: 'Backstage UI/TablePagination', component: TablePagination, argTypes: { @@ -30,12 +27,9 @@ const meta = { setOffset: { action: 'setOffset' }, setPageSize: { action: 'setPageSize' }, }, -} satisfies Meta; +}); -export default meta; -type Story = StoryObj; - -export const Default: Story = { +export const Default = meta.story({ args: { offset: 0, pageSize: 10, @@ -56,4 +50,4 @@ export const Default: Story = { /> ); }, -}; +}); diff --git a/packages/ui/src/components/Tabs/Tabs.stories.tsx b/packages/ui/src/components/Tabs/Tabs.stories.tsx index c818d0fe4b..2a02bedc8c 100644 --- a/packages/ui/src/components/Tabs/Tabs.stories.tsx +++ b/packages/ui/src/components/Tabs/Tabs.stories.tsx @@ -14,19 +14,17 @@ * limitations under the License. */ -import type { Meta, StoryFn, StoryObj } from '@storybook/react-vite'; +import preview from '../../../../../.storybook/preview'; +import type { StoryFn } from '@storybook/react-vite'; import { Tabs, TabList, Tab, TabPanel } from './Tabs'; import { MemoryRouter } from 'react-router-dom'; import { Box } from '../Box'; import { Text } from '../Text'; -const meta = { +const meta = preview.meta({ title: 'Backstage UI/Tabs', component: Tabs, -} satisfies Meta; - -export default meta; -type Story = StoryObj; +}); const withRouter = (Story: StoryFn) => ( @@ -34,7 +32,7 @@ const withRouter = (Story: StoryFn) => ( ); -export const Default: Story = { +export const Default = meta.story({ args: { children: '', }, @@ -48,9 +46,9 @@ export const Default: Story = { ), -}; +}); -export const WithTabPanels: Story = { +export const WithTabPanels = meta.story({ args: { children: '', }, @@ -73,9 +71,9 @@ export const WithTabPanels: Story = { ), -}; +}); -export const WithMockedURLTab2: Story = { +export const WithMockedURLTab2 = meta.story({ args: { children: '', }, @@ -105,9 +103,9 @@ export const WithMockedURLTab2: Story = { ), -}; +}); -export const WithMockedURLTab3: Story = { +export const WithMockedURLTab3 = meta.story({ args: { children: '', }, @@ -137,9 +135,9 @@ export const WithMockedURLTab3: Story = { ), -}; +}); -export const WithMockedURLNoMatch: Story = { +export const WithMockedURLNoMatch = meta.story({ args: { children: '', }, @@ -173,11 +171,11 @@ export const WithMockedURLNoMatch: Story = { ), -}; +}); // New stories for testing match strategies -export const ExactMatchingDefault: Story = { +export const ExactMatchingDefault = meta.story({ args: { children: '', }, @@ -211,9 +209,9 @@ export const ExactMatchingDefault: Story = { ), -}; +}); -export const PrefixMatchingForNestedRoutes: Story = { +export const PrefixMatchingForNestedRoutes = meta.story({ args: { children: '', }, @@ -251,9 +249,9 @@ export const PrefixMatchingForNestedRoutes: Story = { ), -}; +}); -export const PrefixMatchingDeepNesting: Story = { +export const PrefixMatchingDeepNesting = meta.story({ args: { children: '', }, @@ -286,9 +284,9 @@ export const PrefixMatchingDeepNesting: Story = { ), -}; +}); -export const MixedMatchingStrategies: Story = { +export const MixedMatchingStrategies = meta.story({ args: { children: '', }, @@ -337,9 +335,9 @@ export const MixedMatchingStrategies: Story = { ), -}; +}); -export const PrefixMatchingEdgeCases: Story = { +export const PrefixMatchingEdgeCases = meta.story({ args: { children: '', }, @@ -379,9 +377,9 @@ export const PrefixMatchingEdgeCases: Story = { ), -}; +}); -export const PrefixMatchingWithSlash: Story = { +export const PrefixMatchingWithSlash = meta.story({ args: { children: '', }, @@ -420,9 +418,9 @@ export const PrefixMatchingWithSlash: Story = { ), -}; +}); -export const RootPathMatching: Story = { +export const RootPathMatching = meta.story({ args: { children: '', }, @@ -454,9 +452,9 @@ export const RootPathMatching: Story = { ), -}; +}); -export const AutoSelectionOfTabs: Story = { +export const AutoSelectionOfTabs = meta.story({ args: { children: '', }, @@ -511,4 +509,4 @@ export const AutoSelectionOfTabs: Story = { ), -}; +}); diff --git a/packages/ui/src/components/TagGroup/TagGroup.stories.tsx b/packages/ui/src/components/TagGroup/TagGroup.stories.tsx index 7cc8d3c824..d33eed8bf1 100644 --- a/packages/ui/src/components/TagGroup/TagGroup.stories.tsx +++ b/packages/ui/src/components/TagGroup/TagGroup.stories.tsx @@ -14,8 +14,8 @@ * limitations under the License. */ +import preview from '../../../../../.storybook/preview'; import { useState } from 'react'; -import type { Meta, StoryObj } from '@storybook/react-vite'; import { TagGroup, Tag } from '.'; import type { Selection } from 'react-aria-components'; import { Flex } from '../../'; @@ -35,7 +35,7 @@ export interface ListItem { isDisabled?: boolean; } -const meta = { +const meta = preview.meta({ title: 'Backstage UI/TagGroup', component: TagGroup, argTypes: { @@ -54,10 +54,7 @@ const meta = { ), ], -} satisfies Meta>; - -export default meta; -type Story = StoryObj; +}); const initialList: ListItem[] = [ { id: 'banana', name: 'Banana', icon: }, @@ -74,7 +71,7 @@ const initialList: ListItem[] = [ { id: 'strawberry', name: 'Strawberry', icon: }, ]; -export const Default: Story = { +export const Default = meta.story({ args: { 'aria-label': 'Tag Group', }, @@ -85,11 +82,11 @@ export const Default: Story = { ))} ), -}; +}); -export const Sizes: Story = { +export const Sizes = meta.story({ args: { - ...Default.args, + ...Default.input.args, }, render: args => ( @@ -109,9 +106,9 @@ export const Sizes: Story = { ), -}; +}); -export const SelectionModeSingle: Story = { +export const SelectionModeSingle = meta.story({ args: { selectionMode: 'single', 'aria-label': 'Tag Group', @@ -130,9 +127,9 @@ export const SelectionModeSingle: Story = { ); }, -}; +}); -export const SelectionModeMultiple: Story = { +export const SelectionModeMultiple = meta.story({ args: { selectionMode: 'multiple', 'aria-label': 'Tag Group', @@ -153,11 +150,11 @@ export const SelectionModeMultiple: Story = { ); }, -}; +}); -export const WithIcon: Story = { +export const WithIcon = meta.story({ args: { - ...Default.args, + ...Default.input.args, }, render: args => ( @@ -168,9 +165,9 @@ export const WithIcon: Story = { ))} ), -}; +}); -export const WithLink: Story = { +export const WithLink = meta.story({ render: args => ( {initialList.map(item => ( @@ -180,9 +177,9 @@ export const WithLink: Story = { ))} ), -}; +}); -export const Disabled: Story = { +export const Disabled = meta.story({ render: args => ( {initialList.map(item => ( @@ -192,11 +189,11 @@ export const Disabled: Story = { ))} ), -}; +}); -export const RemovingTags: Story = { +export const RemovingTags = meta.story({ args: { - ...Default.args, + ...Default.input.args, }, render: args => { const [selected, setSelected] = useState(new Set(['travel'])); @@ -217,11 +214,11 @@ export const RemovingTags: Story = { ); }, -}; +}); -export const WithIconAndRemoveButton: Story = { +export const WithIconAndRemoveButton = meta.story({ args: { - ...Default.args, + ...Default.input.args, }, render: args => { const [selected, setSelected] = useState(new Set(['travel'])); @@ -244,4 +241,4 @@ export const WithIconAndRemoveButton: Story = { ); }, -}; +}); diff --git a/packages/ui/src/components/Text/Text.stories.tsx b/packages/ui/src/components/Text/Text.stories.tsx index 7f95f4d5a1..09a317716a 100644 --- a/packages/ui/src/components/Text/Text.stories.tsx +++ b/packages/ui/src/components/Text/Text.stories.tsx @@ -14,31 +14,28 @@ * limitations under the License. */ -import type { Meta, StoryObj } from '@storybook/react-vite'; +import preview from '../../../../../.storybook/preview'; import { Text } from './Text'; import { Flex } from '../Flex'; -const meta = { +const meta = preview.meta({ title: 'Backstage UI/Text', component: Text, args: { children: 'Text', }, -} satisfies Meta; +}); -export default meta; -type Story = StoryObj; - -export const Default: Story = { +export const Default = meta.story({ args: { children: "A man looks at a painting in a museum and says, “Brothers and sisters I have none, but that man's father is my father's son.” Who is in the painting?", }, -}; +}); -export const AllVariants: Story = { +export const AllVariants = meta.story({ args: { - ...Default.args, + ...Default.input.args, }, render: args => ( @@ -52,9 +49,9 @@ export const AllVariants: Story = { ), -}; +}); -export const AllWeights: Story = { +export const AllWeights = meta.story({ render: () => ( @@ -91,11 +88,11 @@ export const AllWeights: Story = { ), -}; +}); -export const AllColors: Story = { +export const AllColors = meta.story({ args: { - ...Default.args, + ...Default.input.args, }, render: args => ( @@ -106,30 +103,30 @@ export const AllColors: Story = { ), -}; +}); -export const Truncate: Story = { +export const Truncate = meta.story({ args: { - ...Default.args, + ...Default.input.args, truncate: true, as: 'p', style: { width: '480px' }, }, -}; +}); -export const Responsive: Story = { +export const Responsive = meta.story({ args: { - ...Default.args, + ...Default.input.args, variant: { xs: 'title-x-small', md: 'body-large', }, }, -}; +}); -export const WrappedInLink: Story = { +export const WrappedInLink = meta.story({ args: { - ...Default.args, + ...Default.input.args, }, decorators: [ Story => ( @@ -138,16 +135,16 @@ export const WrappedInLink: Story = { ), ], -}; +}); -export const CustomRender: Story = { +export const CustomRender = meta.story({ args: { - ...Default.args, + ...Default.input.args, as: 'label', }, -}; +}); -export const Playground: Story = { +export const Playground = meta.story({ render: () => ( Subtitle @@ -176,4 +173,4 @@ export const Playground: Story = { ), -}; +}); diff --git a/packages/ui/src/components/TextField/TextField.stories.tsx b/packages/ui/src/components/TextField/TextField.stories.tsx index 38115996ae..54457f351b 100644 --- a/packages/ui/src/components/TextField/TextField.stories.tsx +++ b/packages/ui/src/components/TextField/TextField.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. @@ -13,15 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import type { Meta, StoryObj } from '@storybook/react-vite'; +import preview from '../../../../../.storybook/preview'; import { TextField } from './TextField'; import { Form } from 'react-aria-components'; import { Flex } from '../Flex'; import { FieldLabel } from '../FieldLabel'; import { RiEyeLine, RiSparklingLine } from '@remixicon/react'; -const meta = { +const meta = preview.meta({ title: 'Backstage UI/TextField', component: TextField, argTypes: { @@ -32,12 +31,9 @@ const meta = { control: 'object', }, }, -} satisfies Meta; +}); -export default meta; -type Story = StoryObj; - -export const Default: Story = { +export const Default = meta.story({ args: { name: 'url', placeholder: 'Enter a URL', @@ -45,11 +41,11 @@ export const Default: Story = { maxWidth: '300px', }, }, -}; +}); -export const Sizes: Story = { +export const Sizes = meta.story({ args: { - ...Default.args, + ...Default.input.args, }, render: args => ( @@ -57,46 +53,46 @@ export const Sizes: Story = { } /> ), -}; +}); -export const DefaultValue: Story = { +export const DefaultValue = meta.story({ args: { - ...Default.args, + ...Default.input.args, defaultValue: 'https://example.com', }, -}; +}); -export const WithLabel: Story = { +export const WithLabel = meta.story({ args: { - ...Default.args, + ...Default.input.args, label: 'Label', }, -}; +}); -export const WithDescription: Story = { +export const WithDescription = meta.story({ args: { - ...WithLabel.args, + ...WithLabel.input.args, description: 'Description', }, -}; +}); -export const Required: Story = { +export const Required = meta.story({ args: { - ...WithLabel.args, + ...WithLabel.input.args, isRequired: true, }, -}; +}); -export const Disabled: Story = { +export const Disabled = meta.story({ args: { - ...Default.args, + ...Default.input.args, isDisabled: true, }, -}; +}); -export const WithIcon: Story = { +export const WithIcon = meta.story({ args: { - ...Default.args, + ...Default.input.args, }, render: args => ( } /> ), -}; +}); -export const DisabledWithIcon: Story = { +export const DisabledWithIcon = meta.story({ args: { - ...WithIcon.args, + ...WithIcon.input.args, isDisabled: true, }, - render: WithIcon.render, -}; + render: WithIcon.input.render, +}); -export const ShowError: Story = { +export const ShowError = meta.story({ args: { - ...WithLabel.args, + ...WithLabel.input.args, }, render: args => (
), -}; +}); -export const Validation: Story = { +export const Validation = meta.story({ args: { - ...WithLabel.args, + ...WithLabel.input.args, validate: value => (value === 'admin' ? 'Nice try!' : null), }, -}; +}); -export const CustomField: Story = { +export const CustomField = meta.story({ render: () => ( <> ), -}; +}); diff --git a/packages/ui/src/components/Tooltip/Tooltip.stories.tsx b/packages/ui/src/components/Tooltip/Tooltip.stories.tsx index cc6b513ed4..a636a356ef 100644 --- a/packages/ui/src/components/Tooltip/Tooltip.stories.tsx +++ b/packages/ui/src/components/Tooltip/Tooltip.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. @@ -13,13 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import type { Meta, StoryObj } from '@storybook/react-vite'; -import { Placement } from '@react-types/overlays'; +import preview from '../../../../../.storybook/preview'; import { TooltipTrigger, Tooltip } from './Tooltip'; import { Button } from '../Button/Button'; -const meta = { +const meta = preview.meta({ title: 'Backstage UI/Tooltip', component: TooltipTrigger, parameters: { layout: 'centered' }, @@ -30,10 +28,6 @@ const meta = { isDisabled: { control: { type: 'boolean' }, }, - placement: { - options: ['top', 'right', 'bottom', 'left'], - control: { type: 'inline-radio' }, - }, delay: { control: { type: 'number' }, }, @@ -41,7 +35,7 @@ const meta = { control: { type: 'number' }, }, }, - render: ({ children, isOpen, isDisabled, placement, delay, closeDelay }) => ( + render: ({ children, isOpen, isDisabled, delay, closeDelay }) => ( - {children ?? 'I am a tooltip'} + {children ?? 'I am a tooltip'} ), -} as Meta<{ - children?: string; - isOpen?: boolean; - isDisabled?: boolean; - placement?: Placement; - delay?: number; - closeDelay?: number; -}>; +}); -export default meta; -type Story = StoryObj; - -export const Default: Story = { +export const Default = meta.story({ args: { children: 'I am a tooltip', }, -}; +}); -export const IsOpen: Story = { +export const IsOpen = meta.story({ args: { - ...Default.args, + ...Default.input.args, isOpen: true, }, -}; +}); -export const IsDisabled: Story = { +export const IsDisabled = meta.story({ args: { - ...Default.args, + ...Default.input.args, isDisabled: true, }, -}; +}); -export const NoDelays: Story = { +export const NoDelays = meta.story({ args: { - ...Default.args, + ...Default.input.args, delay: 0, closeDelay: 0, }, -}; +}); -export const OrthogonalPlacements: Story = { +export const OrthogonalPlacements = meta.story({ parameters: { controls: { exclude: ['placement'], }, }, args: { - ...Default.args, + ...Default.input.args, isOpen: true, }, render: ({ isOpen, children }) => { @@ -113,13 +97,13 @@ export const OrthogonalPlacements: Story = { ); }, -}; +}); -export const WithLongText: Story = { +export const WithLongText = meta.story({ args: { - ...Default.args, + ...Default.input.args, isOpen: true, children: 'I am a tooltip with a very long text. orem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', }, -}; +}); diff --git a/packages/ui/src/components/VisuallyHidden/VisuallyHidden.stories.tsx b/packages/ui/src/components/VisuallyHidden/VisuallyHidden.stories.tsx index 9706edfc9b..cc75adbd5e 100644 --- a/packages/ui/src/components/VisuallyHidden/VisuallyHidden.stories.tsx +++ b/packages/ui/src/components/VisuallyHidden/VisuallyHidden.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. @@ -13,13 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import type { Meta, StoryObj } from '@storybook/react-vite'; +import preview from '../../../../../.storybook/preview'; import { VisuallyHidden } from './VisuallyHidden'; import { Text } from '../Text'; import { Flex } from '../Flex'; -const meta = { +const meta = preview.meta({ title: 'Backstage UI/VisuallyHidden', component: VisuallyHidden, parameters: { @@ -30,12 +29,9 @@ const meta = { }, }, }, -} satisfies Meta; +}); -export default meta; -type Story = StoryObj; - -export const Default: Story = { +export const Default = meta.story({ render: () => ( @@ -48,9 +44,9 @@ export const Default: Story = { ), -}; +}); -export const ExampleUsage: Story = { +export const ExampleUsage = meta.story({ render: () => ( @@ -70,4 +66,4 @@ export const ExampleUsage: Story = { ), -}; +}); diff --git a/packages/ui/src/css/core.css b/packages/ui/src/css/core.css index 5be23e7a1b..321ca07eff 100644 --- a/packages/ui/src/css/core.css +++ b/packages/ui/src/css/core.css @@ -27,7 +27,7 @@ } body { - background-color: var(--bui-bg); + background-color: var(--bui-bg-surface-0); /* Text defaults */ color: var(--bui-fg-primary); diff --git a/packages/ui/src/css/tokens.css b/packages/ui/src/css/tokens.css index 028c60311d..459d98d48f 100644 --- a/packages/ui/src/css/tokens.css +++ b/packages/ui/src/css/tokens.css @@ -81,9 +81,10 @@ --bui-gray-8: #595959; /* Background Colors */ - --bui-bg: var(--bui-gray-1); + --bui-bg-surface-0: var(--bui-gray-1); --bui-bg-surface-1: var(--bui-white); - --bui-bg-surface-2: var(--bui-gray-2); + --bui-bg-surface-2: var(--bui-gray-1); + --bui-bg-surface-3: var(--bui-gray-2); --bui-bg-solid: #1f5493; --bui-bg-solid-hover: #163a66; --bui-bg-solid-pressed: #0f2b4e; @@ -140,9 +141,10 @@ --bui-gray-8: #b4b4b4; /* Background Colors */ - --bui-bg: #333333; - --bui-bg-surface-1: #424242; + --bui-bg-surface-0: #333333; + --bui-bg-surface-1: var(--bui-gray-1); --bui-bg-surface-2: var(--bui-gray-2); + --bui-bg-surface-3: var(--bui-gray-3); --bui-bg-solid: #9cc9ff; --bui-bg-solid-hover: #83b9fd; --bui-bg-solid-pressed: #83b9fd; diff --git a/packages/yarn-plugin/CHANGELOG.md b/packages/yarn-plugin/CHANGELOG.md index 9ca9cbdf6f..20d37cf076 100644 --- a/packages/yarn-plugin/CHANGELOG.md +++ b/packages/yarn-plugin/CHANGELOG.md @@ -1,5 +1,21 @@ # yarn-plugin-backstage +## 0.0.8 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.16 + +## 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 + ## 0.0.7 ### Patch Changes diff --git a/packages/yarn-plugin/package.json b/packages/yarn-plugin/package.json index 4bd43fce40..de8b7955d3 100644 --- a/packages/yarn-plugin/package.json +++ b/packages/yarn-plugin/package.json @@ -1,6 +1,6 @@ { "name": "yarn-plugin-backstage", - "version": "0.0.7", + "version": "0.0.8", "description": "Yarn plugin for working with Backstage monorepos", "backstage": { "role": "node-library" diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index 808fdd9a2a..58db7fac24 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,49 @@ # @backstage/plugin-api-docs +## 0.13.2 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/plugin-catalog@1.32.1 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/plugin-permission-react@0.4.39 + +## 0.13.2-next.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- Updated dependencies + - @backstage/plugin-catalog@1.32.1-next.1 + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-model@1.7.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-react@0.4.39-next.0 + +## 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 + ## 0.13.1 ### Patch Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index f89c75b8f7..fd387bdbae 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-api-docs", - "version": "0.13.1", + "version": "0.13.2", "description": "A Backstage plugin that helps represent API entities in the frontend", "backstage": { "role": "frontend-plugin", @@ -55,7 +55,6 @@ "dependencies": { "@asyncapi/react-component": "^2.3.3", "@backstage/catalog-model": "workspace:^", - "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", diff --git a/plugins/api-docs/report-alpha.api.md b/plugins/api-docs/report-alpha.api.md index ae09605cfc..1a035bc706 100644 --- a/plugins/api-docs/report-alpha.api.md +++ b/plugins/api-docs/report-alpha.api.md @@ -12,13 +12,13 @@ import { EntityCardType } from '@backstage/plugin-catalog-react/alpha'; import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; -import { ExtensionInput } from '@backstage/frontend-plugin-api'; -import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; -import { IconComponent } from '@backstage/core-plugin-api'; +import { ExternalRouteRef } from '@backstage/core-plugin-api'; +import { IconComponent } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; -import { RouteRef } from '@backstage/frontend-plugin-api'; +import { RouteRef } from '@backstage/core-plugin-api'; +import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; import { TranslationRef } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) @@ -63,7 +63,7 @@ const _default: OverridableFrontendPlugin< root: RouteRef; }, { - registerApi: ExternalRouteRef; + registerApi: ExternalRouteRef; }, { 'api:api-docs/config': OverridableExtensionDefinition<{ @@ -346,7 +346,7 @@ const _default: OverridableFrontendPlugin< | ExtensionDataRef | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; @@ -383,7 +383,7 @@ const _default: OverridableFrontendPlugin< defaultGroup?: [Error: `Use the 'group' param instead`]; group?: keyof defaultEntityContentGroups | (string & {}); loader: () => Promise; - routeRef?: RouteRef; + routeRef?: RouteRef_2; filter?: string | EntityPredicate | ((entity: Entity) => boolean); }; }>; @@ -406,7 +406,7 @@ const _default: OverridableFrontendPlugin< | ExtensionDataRef | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; @@ -443,7 +443,7 @@ const _default: OverridableFrontendPlugin< defaultGroup?: [Error: `Use the 'group' param instead`]; group?: keyof defaultEntityContentGroups | (string & {}); loader: () => Promise; - routeRef?: RouteRef; + routeRef?: RouteRef_2; filter?: string | EntityPredicate | ((entity: Entity) => boolean); }; }>; @@ -456,7 +456,7 @@ const _default: OverridableFrontendPlugin< { title: string; icon: IconComponent; - routeRef: RouteRef; + routeRef: RouteRef_2; }, 'core.nav-item.target', {} @@ -465,46 +465,36 @@ const _default: OverridableFrontendPlugin< params: { title: string; icon: IconComponent; - routeRef: RouteRef; + routeRef: RouteRef_2; }; }>; 'page:api-docs': OverridableExtensionDefinition<{ config: { initiallySelectedFilter: 'all' | 'owned' | 'starred' | undefined; - } & { path: string | undefined; }; configInput: { initiallySelectedFilter?: 'all' | 'owned' | 'starred' | undefined; - } & { path?: string | undefined; }; output: | ExtensionDataRef | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; } >; - inputs: { - [x: string]: ExtensionInput< - ExtensionDataRef, - { - singleton: boolean; - optional: boolean; - } - >; - }; + inputs: {}; kind: 'page'; name: undefined; params: { defaultPath?: [Error: `Use the 'path' param instead`]; path: string; loader: () => Promise; - routeRef?: RouteRef; + routeRef?: RouteRef_2; }; }>; } diff --git a/plugins/api-docs/report.api.md b/plugins/api-docs/report.api.md index fb37b2b33b..5c84544319 100644 --- a/plugins/api-docs/report.api.md +++ b/plugins/api-docs/report.api.md @@ -4,7 +4,7 @@ ```ts import { ApiEntity } from '@backstage/catalog-model'; -import { ApiRef } from '@backstage/core-plugin-api'; +import { ApiRef } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { CatalogTableRow } from '@backstage/plugin-catalog'; import { ComponentEntity } from '@backstage/catalog-model'; diff --git a/plugins/api-docs/src/alpha.tsx b/plugins/api-docs/src/alpha.tsx index 7693b97259..73b1c008ab 100644 --- a/plugins/api-docs/src/alpha.tsx +++ b/plugins/api-docs/src/alpha.tsx @@ -23,11 +23,6 @@ import { createFrontendPlugin, } from '@backstage/frontend-plugin-api'; -import { - compatWrapper, - convertLegacyRouteRef, -} from '@backstage/core-compat-api'; - import { ApiEntity, parseEntityRef, @@ -47,8 +42,8 @@ import { const apiDocsNavItem = NavItemBlueprint.make({ params: { title: 'APIs', - routeRef: convertLegacyRouteRef(rootRoute), - icon: () => compatWrapper(), + routeRef: rootRoute, + icon: () => , }, }); @@ -80,15 +75,13 @@ const apiDocsExplorerPage = PageBlueprint.makeWithOverrides({ factory(originalFactory, { config }) { return originalFactory({ path: '/api-docs', - routeRef: convertLegacyRouteRef(rootRoute), + routeRef: rootRoute, loader: () => - import('./components/ApiExplorerPage').then(m => - compatWrapper( - , - ), - ), + import('./components/ApiExplorerPage').then(m => ( + + )), }); }, }); @@ -109,10 +102,7 @@ const apiDocsHasApisEntityCard = EntityCardBlueprint.make({ )!! ); }, - loader: () => - import('./components/ApisCards').then(m => - compatWrapper(), - ), + loader: () => import('./components/ApisCards').then(m => ), }, }); @@ -121,9 +111,9 @@ const apiDocsDefinitionEntityCard = EntityCardBlueprint.make({ params: { filter: 'kind:api', loader: () => - import('./components/ApiDefinitionCard').then(m => - compatWrapper(), - ), + import('./components/ApiDefinitionCard').then(m => ( + + )), }, }); @@ -135,9 +125,7 @@ const apiDocsConsumedApisEntityCard = EntityCardBlueprint.make({ // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 filter: 'kind:component', loader: () => - import('./components/ApisCards').then(m => - compatWrapper(), - ), + import('./components/ApisCards').then(m => ), }, }); @@ -149,9 +137,7 @@ const apiDocsProvidedApisEntityCard = EntityCardBlueprint.make({ // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 filter: 'kind:component', loader: () => - import('./components/ApisCards').then(m => - compatWrapper(), - ), + import('./components/ApisCards').then(m => ), }, }); @@ -163,9 +149,9 @@ const apiDocsConsumingComponentsEntityCard = EntityCardBlueprint.make({ // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 filter: 'kind:api', loader: () => - import('./components/ComponentsCards').then(m => - compatWrapper(), - ), + import('./components/ComponentsCards').then(m => ( + + )), }, }); @@ -177,9 +163,9 @@ const apiDocsProvidingComponentsEntityCard = EntityCardBlueprint.make({ // See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 filter: 'kind:api', loader: () => - import('./components/ComponentsCards').then(m => - compatWrapper(), - ), + import('./components/ComponentsCards').then(m => ( + + )), }, }); @@ -190,15 +176,13 @@ const apiDocsDefinitionEntityContent = EntityContentBlueprint.make({ title: 'Definition', filter: 'kind:api', loader: async () => - import('./components/ApiDefinitionCard').then(m => - compatWrapper( - - - - - , - ), - ), + import('./components/ApiDefinitionCard').then(m => ( + + + + + + )), }, }); @@ -209,18 +193,16 @@ const apiDocsApisEntityContent = EntityContentBlueprint.make({ title: 'APIs', filter: 'kind:component', loader: async () => - import('./components/ApisCards').then(m => - compatWrapper( - - - - - - - - , - ), - ), + import('./components/ApisCards').then(m => ( + + + + + + + + + )), }, }); @@ -228,10 +210,10 @@ export default createFrontendPlugin({ pluginId: 'api-docs', info: { packageJson: () => import('../package.json') }, routes: { - root: convertLegacyRouteRef(rootRoute), + root: rootRoute, }, externalRoutes: { - registerApi: convertLegacyRouteRef(registerComponentRouteRef), + registerApi: registerComponentRouteRef, }, extensions: [ apiDocsNavItem, diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md index ccb3ebdaab..1923dd74a3 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,43 @@ # @backstage/plugin-app-backend +## 0.5.9 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-app-node@0.1.40 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/config-loader@1.10.7 + +## 0.5.9-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-app-node@0.1.40-next.1 + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config-loader@1.10.7-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 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 + ## 0.5.8 ### Patch Changes diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index 3c3bba9c1c..9ab956713f 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app-backend", - "version": "0.5.8", + "version": "0.5.9", "description": "A Backstage backend plugin that serves the Backstage frontend app", "backstage": { "role": "backend-plugin", @@ -60,7 +60,7 @@ "@backstage/plugin-app-node": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "@backstage/types": "workspace:^", - "express": "^4.17.1", + "express": "^4.22.0", "express-promise-router": "^4.1.0", "fs-extra": "^11.2.0", "globby": "^11.0.0", diff --git a/plugins/app-node/CHANGELOG.md b/plugins/app-node/CHANGELOG.md index 8ffa51a053..ceab019e5a 100644 --- a/plugins/app-node/CHANGELOG.md +++ b/plugins/app-node/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-app-node +## 0.1.40 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/config-loader@1.10.7 + +## 0.1.40-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config-loader@1.10.7-next.1 + +## 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 + ## 0.1.39 ### Patch Changes diff --git a/plugins/app-node/package.json b/plugins/app-node/package.json index 5dc2aee403..c41733eb04 100644 --- a/plugins/app-node/package.json +++ b/plugins/app-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app-node", - "version": "0.1.39", + "version": "0.1.40", "description": "Node.js library for the app plugin", "backstage": { "role": "node-library", @@ -39,7 +39,7 @@ "@backstage/backend-plugin-api": "workspace:^", "@backstage/config-loader": "workspace:^", "@types/express": "^4.17.6", - "express": "^4.17.1", + "express": "^4.22.0", "fs-extra": "^11.2.0" }, "devDependencies": { diff --git a/plugins/app-visualizer/CHANGELOG.md b/plugins/app-visualizer/CHANGELOG.md index 986a3d51bc..22cff4ea85 100644 --- a/plugins/app-visualizer/CHANGELOG.md +++ b/plugins/app-visualizer/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-app-visualizer +## 0.1.26 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/ui@0.10.0 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + +## 0.1.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.10.0-next.1 + - @backstage/core-components@0.18.4-next.1 + +## 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 + ## 0.1.25 ### Patch Changes diff --git a/plugins/app-visualizer/package.json b/plugins/app-visualizer/package.json index 35adbbbb95..6ece031af4 100644 --- a/plugins/app-visualizer/package.json +++ b/plugins/app-visualizer/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app-visualizer", - "version": "0.1.25", + "version": "0.1.26", "description": "Visualizes the Backstage app structure", "backstage": { "role": "frontend-plugin", diff --git a/plugins/app-visualizer/report.api.md b/plugins/app-visualizer/report.api.md index f3b7e6ae61..180507677e 100644 --- a/plugins/app-visualizer/report.api.md +++ b/plugins/app-visualizer/report.api.md @@ -5,7 +5,7 @@ ```ts import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; -import { IconComponent } from '@backstage/core-plugin-api'; +import { IconComponent } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; diff --git a/plugins/app/CHANGELOG.md b/plugins/app/CHANGELOG.md index 2035885939..66e1ca5b0b 100644 --- a/plugins/app/CHANGELOG.md +++ b/plugins/app/CHANGELOG.md @@ -1,5 +1,51 @@ # @backstage/plugin-app +## 0.3.3 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- f7bc228: Support to set `defaultLanguage` and `availableLanguages` for the app language API in the new frontend system +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-permission-react@0.4.39 + +## 0.3.3-next.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- f7bc228: Support to set `defaultLanguage` and `availableLanguages` for the app language API in the new frontend system +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/core-components@0.18.4-next.2 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-permission-react@0.4.39-next.0 + +## 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 + ## 0.3.2 ### Patch Changes diff --git a/plugins/app/package.json b/plugins/app/package.json index c43c664bf7..a6a25d08ad 100644 --- a/plugins/app/package.json +++ b/plugins/app/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app", - "version": "0.3.2", + "version": "0.3.3", "backstage": { "role": "frontend-plugin", "pluginId": "app", diff --git a/plugins/app/report.api.md b/plugins/app/report.api.md index bd8c05bfe4..ea34a1731f 100644 --- a/plugins/app/report.api.md +++ b/plugins/app/report.api.md @@ -13,15 +13,14 @@ import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; -import { IconComponent } from '@backstage/core-plugin-api'; -import { IconComponent as IconComponent_2 } from '@backstage/frontend-plugin-api'; +import { IconComponent } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { NavContentComponent } from '@backstage/frontend-plugin-api'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; import { ReactNode } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; -import { SignInPageProps } from '@backstage/core-plugin-api'; +import { SignInPageProps } from '@backstage/frontend-plugin-api'; import { SwappableComponentRef } from '@backstage/frontend-plugin-api'; import { TranslationMessages } from '@backstage/frontend-plugin-api'; import { TranslationResource } from '@backstage/frontend-plugin-api'; @@ -233,12 +232,18 @@ const appPlugin: OverridableFrontendPlugin< ) => ExtensionBlueprintParams; }>; 'api:app/app-language': OverridableExtensionDefinition<{ - kind: 'api'; - name: 'app-language'; - config: {}; - configInput: {}; + config: { + defaultLanguage: string | undefined; + availableLanguages: string[] | undefined; + }; + configInput: { + defaultLanguage?: string | undefined; + availableLanguages?: string[] | undefined; + }; output: ExtensionDataRef; inputs: {}; + kind: 'api'; + name: 'app-language'; params: < TApi, TImpl extends TApi, @@ -458,7 +463,7 @@ const appPlugin: OverridableFrontendPlugin< icons: ExtensionInput< ConfigurableExtensionDataRef< { - [x: string]: IconComponent_2; + [x: string]: IconComponent; }, 'core.icons', {} @@ -713,15 +718,7 @@ const appPlugin: OverridableFrontendPlugin< transientTimeoutMs?: number | undefined; }; output: ExtensionDataRef; - inputs: { - [x: string]: ExtensionInput< - ExtensionDataRef, - { - singleton: boolean; - optional: boolean; - } - >; - }; + inputs: {}; kind: 'app-root-element'; name: 'alert-display'; params: { @@ -732,15 +729,7 @@ const appPlugin: OverridableFrontendPlugin< config: {}; configInput: {}; output: ExtensionDataRef; - inputs: { - [x: string]: ExtensionInput< - ExtensionDataRef, - { - singleton: boolean; - optional: boolean; - } - >; - }; + inputs: {}; kind: 'app-root-element'; name: 'dialog-display'; params: { diff --git a/plugins/app/src/extensions/AppLanguageApi.ts b/plugins/app/src/extensions/AppLanguageApi.ts index 325dffa2fd..4f605aa30f 100644 --- a/plugins/app/src/extensions/AppLanguageApi.ts +++ b/plugins/app/src/extensions/AppLanguageApi.ts @@ -19,12 +19,25 @@ import { AppLanguageSelector } from '../../../../packages/core-app-api/src/apis/ import { appLanguageApiRef } from '@backstage/core-plugin-api/alpha'; import { ApiBlueprint } from '@backstage/frontend-plugin-api'; -export const AppLanguageApi = ApiBlueprint.make({ +export const AppLanguageApi = ApiBlueprint.makeWithOverrides({ name: 'app-language', - params: defineParams => - defineParams({ - api: appLanguageApiRef, - deps: {}, - factory: () => AppLanguageSelector.createWithStorage(), - }), + config: { + schema: { + defaultLanguage: z => z.string().optional(), + availableLanguages: z => z.array(z.string()).optional(), + }, + }, + factory(originalFactory, { config }) { + return originalFactory(defineParams => + defineParams({ + api: appLanguageApiRef, + deps: {}, + factory: () => + AppLanguageSelector.createWithStorage({ + defaultLanguage: config.defaultLanguage, + availableLanguages: config.availableLanguages, + }), + }), + ); + }, }); diff --git a/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md b/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md index 6bde076376..b78cce757e 100644 --- a/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-auth-backend-module-atlassian-provider +## 0.4.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.4.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 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 + ## 0.4.9 ### Patch Changes diff --git a/plugins/auth-backend-module-atlassian-provider/package.json b/plugins/auth-backend-module-atlassian-provider/package.json index f12ff657e3..28f4031eab 100644 --- a/plugins/auth-backend-module-atlassian-provider/package.json +++ b/plugins/auth-backend-module-atlassian-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-atlassian-provider", - "version": "0.4.9", + "version": "0.4.10", "description": "The atlassian-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -36,7 +36,7 @@ "dependencies": { "@backstage/backend-plugin-api": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", - "express": "^4.18.2", + "express": "^4.22.0", "passport": "^0.7.0", "passport-atlassian-oauth2": "^2.1.0", "zod": "^3.22.4" diff --git a/plugins/auth-backend-module-auth0-provider/CHANGELOG.md b/plugins/auth-backend-module-auth0-provider/CHANGELOG.md index bb860d2dfd..6e9806b643 100644 --- a/plugins/auth-backend-module-auth0-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-auth0-provider/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-auth-backend-module-auth0-provider +## 0.2.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.2.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 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 + ## 0.2.9 ### Patch Changes diff --git a/plugins/auth-backend-module-auth0-provider/package.json b/plugins/auth-backend-module-auth0-provider/package.json index e58d80a053..8b749bc420 100644 --- a/plugins/auth-backend-module-auth0-provider/package.json +++ b/plugins/auth-backend-module-auth0-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-auth0-provider", - "version": "0.2.9", + "version": "0.2.10", "description": "The auth0-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -36,7 +36,7 @@ "dependencies": { "@backstage/backend-plugin-api": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", - "express": "^4.17.1", + "express": "^4.22.0", "passport-auth0": "^1.4.3", "passport-oauth2": "^1.6.1" }, diff --git a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md index aaf6f60131..aff6333c3c 100644 --- a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-auth-backend-module-aws-alb-provider +## 0.4.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-auth-backend@0.25.7 + - @backstage/backend-plugin-api@1.6.0 + +## 0.4.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.7-next.1 + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/errors@1.2.7 + +## 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 + ## 0.4.9 ### Patch Changes diff --git a/plugins/auth-backend-module-aws-alb-provider/package.json b/plugins/auth-backend-module-aws-alb-provider/package.json index 8e9b3dfda8..65ef39f0d5 100644 --- a/plugins/auth-backend-module-aws-alb-provider/package.json +++ b/plugins/auth-backend-module-aws-alb-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-aws-alb-provider", - "version": "0.4.9", + "version": "0.4.10", "description": "The aws-alb provider module for the Backstage auth backend.", "backstage": { "role": "backend-plugin-module", @@ -50,7 +50,7 @@ "@backstage/cli": "workspace:^", "@backstage/config": "workspace:^", "@backstage/types": "workspace:^", - "express": "^4.18.2", + "express": "^4.22.0", "msw": "^2.0.8" } } diff --git a/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md b/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md index 5e1c17c705..4829c70373 100644 --- a/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-auth-backend-module-azure-easyauth-provider +## 0.2.15 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.2.15-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + +## 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 + ## 0.2.14 ### Patch Changes diff --git a/plugins/auth-backend-module-azure-easyauth-provider/package.json b/plugins/auth-backend-module-azure-easyauth-provider/package.json index 16327c7690..ba0b80c93c 100644 --- a/plugins/auth-backend-module-azure-easyauth-provider/package.json +++ b/plugins/auth-backend-module-azure-easyauth-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-azure-easyauth-provider", - "version": "0.2.14", + "version": "0.2.15", "description": "The azure-easyauth-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -38,7 +38,7 @@ "@backstage/errors": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "@types/passport": "^1.0.16", - "express": "^4.19.2", + "express": "^4.22.0", "jose": "^5.0.0", "passport": "^0.7.0", "zod": "^3.22.4" diff --git a/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md b/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md index ddbab777d9..0870c56aff 100644 --- a/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-auth-backend-module-bitbucket-provider +## 0.3.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.3.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 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 + ## 0.3.9 ### Patch Changes diff --git a/plugins/auth-backend-module-bitbucket-provider/package.json b/plugins/auth-backend-module-bitbucket-provider/package.json index 32761c010c..83efe1ea70 100644 --- a/plugins/auth-backend-module-bitbucket-provider/package.json +++ b/plugins/auth-backend-module-bitbucket-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-bitbucket-provider", - "version": "0.3.9", + "version": "0.3.10", "description": "The bitbucket-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -36,7 +36,7 @@ "dependencies": { "@backstage/backend-plugin-api": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", - "express": "^4.18.2", + "express": "^4.22.0", "passport": "^0.7.0", "passport-bitbucket-oauth2": "^0.1.2", "zod": "^3.22.4" diff --git a/plugins/auth-backend-module-bitbucket-server-provider/CHANGELOG.md b/plugins/auth-backend-module-bitbucket-server-provider/CHANGELOG.md index cfab09eb79..2f9860e317 100644 --- a/plugins/auth-backend-module-bitbucket-server-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-bitbucket-server-provider/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-auth-backend-module-bitbucket-server-provider +## 0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.2.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 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 + ## 0.2.9 ### Patch Changes diff --git a/plugins/auth-backend-module-bitbucket-server-provider/package.json b/plugins/auth-backend-module-bitbucket-server-provider/package.json index 6129c6de0b..701fe9d337 100644 --- a/plugins/auth-backend-module-bitbucket-server-provider/package.json +++ b/plugins/auth-backend-module-bitbucket-server-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-bitbucket-server-provider", - "version": "0.2.9", + "version": "0.2.10", "description": "The bitbucket-server-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md b/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md index ebd323cd73..06d2bf174c 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-auth-backend-module-cloudflare-access-provider +## 0.4.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.4.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + +## 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 + ## 0.4.9 ### Patch Changes diff --git a/plugins/auth-backend-module-cloudflare-access-provider/package.json b/plugins/auth-backend-module-cloudflare-access-provider/package.json index 0cb0b9eb1d..0cea95105c 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/package.json +++ b/plugins/auth-backend-module-cloudflare-access-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-cloudflare-access-provider", - "version": "0.4.9", + "version": "0.4.10", "description": "The cloudflare-access-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -38,7 +38,7 @@ "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", - "express": "^4.18.2", + "express": "^4.22.0", "jose": "^5.0.0", "zod": "^3.22.4" }, diff --git a/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md b/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md index 8601a01745..1243523582 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-auth-backend-module-gcp-iap-provider +## 0.4.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.4.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 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 + ## 0.4.9 ### Patch Changes diff --git a/plugins/auth-backend-module-gcp-iap-provider/package.json b/plugins/auth-backend-module-gcp-iap-provider/package.json index 8f7e73f31d..e045da8fc8 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/package.json +++ b/plugins/auth-backend-module-gcp-iap-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-gcp-iap-provider", - "version": "0.4.9", + "version": "0.4.10", "description": "A GCP IAP auth provider module for the Backstage auth backend", "backstage": { "role": "backend-plugin-module", @@ -48,7 +48,7 @@ "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "express": "^4.18.2" + "express": "^4.22.0" }, "configSchema": "config.d.ts" } diff --git a/plugins/auth-backend-module-github-provider/CHANGELOG.md b/plugins/auth-backend-module-github-provider/CHANGELOG.md index 79f7f297d5..a8458f6b1d 100644 --- a/plugins/auth-backend-module-github-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-github-provider/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-auth-backend-module-github-provider +## 0.4.0 + +### Minor Changes + +- b3286d5: Added the `github.com/user-id` annotation to store GitHub's user ID (immutable) in user entities. Also includes addition of the `userIdMatchingUserEntityAnnotation` sign-in resolver that matches users by the new ID. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 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 + ## 0.3.9 ### Patch Changes diff --git a/plugins/auth-backend-module-github-provider/package.json b/plugins/auth-backend-module-github-provider/package.json index 5e86e2c396..ce418e2b08 100644 --- a/plugins/auth-backend-module-github-provider/package.json +++ b/plugins/auth-backend-module-github-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-github-provider", - "version": "0.3.9", + "version": "0.4.0", "description": "The github-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-github-provider/report.api.md b/plugins/auth-backend-module-github-provider/report.api.md index 6bad840d00..4581115679 100644 --- a/plugins/auth-backend-module-github-provider/report.api.md +++ b/plugins/auth-backend-module-github-provider/report.api.md @@ -17,13 +17,25 @@ export default authModuleGithubProvider; // @public (undocumented) export const githubAuthenticator: OAuthAuthenticator< PassportOAuthAuthenticatorHelper, - PassportProfile + GithubProfile >; +// @public (undocumented) +export type GithubProfile = PassportProfile & { + nodeId?: string; +}; + // @public export namespace githubSignInResolvers { const usernameMatchingUserEntityName: SignInResolverFactory< - OAuthAuthenticatorResult, + OAuthAuthenticatorResult, + | { + dangerouslyAllowSignInWithoutUserInCatalog?: boolean | undefined; + } + | undefined + >; + const userIdMatchingUserEntityAnnotation: SignInResolverFactory< + OAuthAuthenticatorResult, | { dangerouslyAllowSignInWithoutUserInCatalog?: boolean | undefined; } diff --git a/plugins/auth-backend-module-github-provider/src/authenticator.ts b/plugins/auth-backend-module-github-provider/src/authenticator.ts index e9dc29e4a6..fa3e2b0987 100644 --- a/plugins/auth-backend-module-github-provider/src/authenticator.ts +++ b/plugins/auth-backend-module-github-provider/src/authenticator.ts @@ -22,10 +22,18 @@ import { PassportProfile, } from '@backstage/plugin-auth-node'; +/** @public */ +export type GithubProfile = PassportProfile & { + nodeId?: string; +}; + const ACCESS_TOKEN_PREFIX = 'access-token-v2.'; /** @public */ -export const githubAuthenticator = createOAuthAuthenticator({ +export const githubAuthenticator = createOAuthAuthenticator< + PassportOAuthAuthenticatorHelper, + GithubProfile +>({ defaultProfileTransform: PassportOAuthAuthenticatorHelper.defaultProfileTransform, scopes: { @@ -90,7 +98,7 @@ export const githubAuthenticator = createOAuthAuthenticator({ if (!session.refreshToken && !session.expiresInSeconds) { session.refreshToken = ACCESS_TOKEN_PREFIX + session.accessToken; } - return { fullProfile, session }; + return { fullProfile: fullProfile as GithubProfile, session }; }, async refresh(input, helper) { @@ -115,7 +123,7 @@ export const githubAuthenticator = createOAuthAuthenticator({ }); return { - fullProfile, + fullProfile: fullProfile as GithubProfile, session: { accessToken, tokenType: 'bearer', diff --git a/plugins/auth-backend-module-github-provider/src/index.ts b/plugins/auth-backend-module-github-provider/src/index.ts index 646ba7c92a..678e188461 100644 --- a/plugins/auth-backend-module-github-provider/src/index.ts +++ b/plugins/auth-backend-module-github-provider/src/index.ts @@ -20,6 +20,6 @@ * @packageDocumentation */ -export { githubAuthenticator } from './authenticator'; +export { githubAuthenticator, type GithubProfile } from './authenticator'; export { authModuleGithubProvider as default } from './module'; export { githubSignInResolvers } from './resolvers'; diff --git a/plugins/auth-backend-module-github-provider/src/resolvers.ts b/plugins/auth-backend-module-github-provider/src/resolvers.ts index 5a6934439f..e065432826 100644 --- a/plugins/auth-backend-module-github-provider/src/resolvers.ts +++ b/plugins/auth-backend-module-github-provider/src/resolvers.ts @@ -17,11 +17,12 @@ import { createSignInResolverFactory, OAuthAuthenticatorResult, - PassportProfile, SignInInfo, } from '@backstage/plugin-auth-node'; import { z } from 'zod'; +import { GithubProfile } from './authenticator'; + /** * Available sign-in resolvers for the GitHub auth provider. * @@ -39,7 +40,7 @@ export namespace githubSignInResolvers { .optional(), create(options = {}) { return async ( - info: SignInInfo>, + info: SignInInfo>, ctx, ) => { const { fullProfile } = info.result; @@ -63,4 +64,44 @@ export namespace githubSignInResolvers { }; }, }); + + /** + * Looks up the user by matching their GitHub user ID to the github.com/user-id annotation. + */ + export const userIdMatchingUserEntityAnnotation = createSignInResolverFactory( + { + optionsSchema: z + .object({ + dangerouslyAllowSignInWithoutUserInCatalog: z.boolean().optional(), + }) + .optional(), + create(options = {}) { + return async ( + info: SignInInfo>, + ctx, + ) => { + const { fullProfile } = info.result; + + const userId = fullProfile.nodeId; + if (!userId) { + throw new Error(`GitHub user profile does not contain a user ID`); + } + + return ctx.signInWithCatalogUser( + { + annotations: { + 'github.com/user-id': userId, + }, + }, + { + dangerousEntityRefFallback: + options?.dangerouslyAllowSignInWithoutUserInCatalog + ? { entityRef: { name: userId } } + : undefined, + }, + ); + }; + }, + }, + ); } diff --git a/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md b/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md index 58d6712600..c31ef87722 100644 --- a/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-auth-backend-module-gitlab-provider +## 0.3.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.3.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 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 + ## 0.3.9 ### Patch Changes diff --git a/plugins/auth-backend-module-gitlab-provider/package.json b/plugins/auth-backend-module-gitlab-provider/package.json index beef074786..81b2450555 100644 --- a/plugins/auth-backend-module-gitlab-provider/package.json +++ b/plugins/auth-backend-module-gitlab-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-gitlab-provider", - "version": "0.3.9", + "version": "0.3.10", "description": "The gitlab-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -36,7 +36,7 @@ "dependencies": { "@backstage/backend-plugin-api": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", - "express": "^4.18.2", + "express": "^4.22.0", "passport": "^0.7.0", "passport-gitlab2": "^5.0.0", "zod": "^3.22.4" diff --git a/plugins/auth-backend-module-google-provider/CHANGELOG.md b/plugins/auth-backend-module-google-provider/CHANGELOG.md index ccc4be07f8..ef7f4a6506 100644 --- a/plugins/auth-backend-module-google-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-google-provider/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-auth-backend-module-google-provider +## 0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 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 + ## 0.3.9 ### Patch Changes diff --git a/plugins/auth-backend-module-google-provider/package.json b/plugins/auth-backend-module-google-provider/package.json index 288062c1c3..bc2523dbdb 100644 --- a/plugins/auth-backend-module-google-provider/package.json +++ b/plugins/auth-backend-module-google-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-google-provider", - "version": "0.3.9", + "version": "0.3.10", "description": "A Google auth provider module for the Backstage auth backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-guest-provider/CHANGELOG.md b/plugins/auth-backend-module-guest-provider/CHANGELOG.md index 38aa29ccd7..221b83037e 100644 --- a/plugins/auth-backend-module-guest-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-guest-provider/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-auth-backend-module-guest-provider +## 0.2.15 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.2.15-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + +## 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 + ## 0.2.14 ### Patch Changes diff --git a/plugins/auth-backend-module-guest-provider/package.json b/plugins/auth-backend-module-guest-provider/package.json index c2517e7fb7..726674a1ae 100644 --- a/plugins/auth-backend-module-guest-provider/package.json +++ b/plugins/auth-backend-module-guest-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-guest-provider", - "version": "0.2.14", + "version": "0.2.15", "description": "The guest-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -44,7 +44,7 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/config": "workspace:^", - "express": "^4.18.2" + "express": "^4.22.0" }, "configSchema": "config.d.ts" } diff --git a/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md b/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md index 5977b75aa9..7e4b112021 100644 --- a/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-auth-backend-module-microsoft-provider +## 0.3.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.3.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 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 + ## 0.3.9 ### Patch Changes diff --git a/plugins/auth-backend-module-microsoft-provider/package.json b/plugins/auth-backend-module-microsoft-provider/package.json index fbb5106e14..10e7b2ebec 100644 --- a/plugins/auth-backend-module-microsoft-provider/package.json +++ b/plugins/auth-backend-module-microsoft-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-microsoft-provider", - "version": "0.3.9", + "version": "0.3.10", "description": "The microsoft-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -36,7 +36,7 @@ "dependencies": { "@backstage/backend-plugin-api": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", - "express": "^4.18.2", + "express": "^4.22.0", "jose": "^5.0.0", "passport-microsoft": "^1.0.0", "zod": "^3.22.4" diff --git a/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md b/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md index 1b24f30d62..8dadd4938e 100644 --- a/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-auth-backend-module-oauth2-provider +## 0.4.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.4.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 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 + ## 0.4.9 ### Patch Changes diff --git a/plugins/auth-backend-module-oauth2-provider/package.json b/plugins/auth-backend-module-oauth2-provider/package.json index 056f7ad093..7ec85a39a0 100644 --- a/plugins/auth-backend-module-oauth2-provider/package.json +++ b/plugins/auth-backend-module-oauth2-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-oauth2-provider", - "version": "0.4.9", + "version": "0.4.10", "description": "The oauth2-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md b/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md index 26c86f1902..faa632f587 100644 --- a/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-auth-backend-module-oauth2-proxy-provider +## 0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.2.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/errors@1.2.7 + +## 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 + ## 0.2.14 ### Patch Changes diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/package.json b/plugins/auth-backend-module-oauth2-proxy-provider/package.json index 112a77fee5..483022a70a 100644 --- a/plugins/auth-backend-module-oauth2-proxy-provider/package.json +++ b/plugins/auth-backend-module-oauth2-proxy-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-oauth2-proxy-provider", - "version": "0.2.14", + "version": "0.2.15", "description": "The oauth2-proxy-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md index 6ee64562dd..1c89ff764f 100644 --- a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-auth-backend-module-oidc-provider +## 0.4.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-auth-backend@0.25.7 + - @backstage/backend-plugin-api@1.6.0 + +## 0.4.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-backend@0.25.7-next.1 + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## 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 + ## 0.4.9 ### Patch Changes diff --git a/plugins/auth-backend-module-oidc-provider/config.d.ts b/plugins/auth-backend-module-oidc-provider/config.d.ts index c11b9319ec..f25d13ae2e 100644 --- a/plugins/auth-backend-module-oidc-provider/config.d.ts +++ b/plugins/auth-backend-module-oidc-provider/config.d.ts @@ -33,6 +33,7 @@ export interface Config { additionalScopes?: string | string[]; prompt?: string; timeout?: HumanDuration | string; + startUrlSearchParams?: [string: string]; signIn?: { resolvers: Array< | { diff --git a/plugins/auth-backend-module-oidc-provider/package.json b/plugins/auth-backend-module-oidc-provider/package.json index 379b1fc4b2..22e753e84e 100644 --- a/plugins/auth-backend-module-oidc-provider/package.json +++ b/plugins/auth-backend-module-oidc-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-oidc-provider", - "version": "0.4.9", + "version": "0.4.10", "description": "The oidc-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -39,7 +39,7 @@ "@backstage/plugin-auth-backend": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "@backstage/types": "workspace:^", - "express": "^4.18.2", + "express": "^4.22.0", "openid-client": "^5.5.0", "passport": "^0.7.0", "zod": "^3.22.4" diff --git a/plugins/auth-backend-module-oidc-provider/report.api.md b/plugins/auth-backend-module-oidc-provider/report.api.md index 11cde22db1..ab7d2bda49 100644 --- a/plugins/auth-backend-module-oidc-provider/report.api.md +++ b/plugins/auth-backend-module-oidc-provider/report.api.md @@ -25,6 +25,7 @@ export const oidcAuthenticator: OAuthAuthenticator< client: BaseClient; strategy: Strategy; }>; + searchParams: Record; }, OidcAuthResult >; diff --git a/plugins/auth-backend-module-oidc-provider/src/authenticator.test.ts b/plugins/auth-backend-module-oidc-provider/src/authenticator.test.ts index c327d4ecc5..1940a71082 100644 --- a/plugins/auth-backend-module-oidc-provider/src/authenticator.test.ts +++ b/plugins/auth-backend-module-oidc-provider/src/authenticator.test.ts @@ -279,6 +279,62 @@ describe('oidcAuthenticator', () => { expect(searchParams.get('response_type')).toBe('code'); }); + it('passes custom start URL search parameters', async () => { + const customImplementation = oidcAuthenticator.initialize({ + callbackUrl: 'https://backstage.test/callback', + config: new ConfigReader({ + metadataUrl: 'https://oidc.test/.well-known/openid-configuration', + clientId: 'clientId123', + clientSecret: 'clientSecret', + startUrlSearchParams: { + foo: '1', + bar: '2', + }, + }), + }); + + const startResponse = await oidcAuthenticator.start( + startRequest, + customImplementation, + ); + + const { searchParams } = new URL(startResponse.url); + + expect(searchParams.get('foo')).toBe('1'); + expect(searchParams.get('bar')).toBe('2'); + }); + + it('does not override the core start URL search parameters with custom ones', async () => { + const customImplementation = oidcAuthenticator.initialize({ + callbackUrl: 'https://backstage.test/callback', + config: new ConfigReader({ + metadataUrl: 'https://oidc.test/.well-known/openid-configuration', + clientId: 'clientId123', + clientSecret: 'clientSecret', + startUrlSearchParams: { + foo: '1', + prompt: 'customPrompt', + scope: 'customScope', + state: 'customState', + nonce: 'customNonce', + }, + }), + }); + + const startResponse = await oidcAuthenticator.start( + startRequest, + customImplementation, + ); + + const { searchParams } = new URL(startResponse.url); + + expect(searchParams.get('foo')).toBe('1'); + expect(searchParams.get('scope')).not.toBe('customScope'); + expect(searchParams.get('state')).not.toBe('customState'); + expect(searchParams.get('nonce')).not.toBe('customNonce'); + expect(searchParams.get('prompt')).not.toBe('customPrompt'); + }); + it('passes a nonce', async () => { const startResponse = await oidcAuthenticator.start( startRequest, diff --git a/plugins/auth-backend-module-oidc-provider/src/authenticator.ts b/plugins/auth-backend-module-oidc-provider/src/authenticator.ts index 6e7022f7cf..316ead3954 100644 --- a/plugins/auth-backend-module-oidc-provider/src/authenticator.ts +++ b/plugins/auth-backend-module-oidc-provider/src/authenticator.ts @@ -83,6 +83,9 @@ export const oidcAuthenticator = createOAuthAuthenticator({ ); const initializedPrompt = config.getOptionalString('prompt'); + const startUrlSearchParams: Record = + config.getOptional('startUrlSearchParams') || {}; + if (config.has('scope')) { throw new Error( 'The oidc provider no longer supports the "scope" configuration option. Please use the "additionalScopes" option instead.', @@ -143,17 +146,21 @@ export const oidcAuthenticator = createOAuthAuthenticator({ return { helper, client, strategy }; }); - return { initializedPrompt, promise }; + return { initializedPrompt, promise, searchParams: startUrlSearchParams }; }, async start(input, ctx) { - const { initializedPrompt, promise } = ctx; + const { initializedPrompt, promise, searchParams } = ctx; const { helper } = await promise; + + // Merge the custom start URL params, but do not override the standard params (scope, state etc) const options: Record = { + ...searchParams, scope: input.scope, state: input.state, nonce: crypto.randomBytes(16).toString('base64'), }; + const prompt = initializedPrompt || 'none'; if (prompt !== 'auto') { options.prompt = prompt; diff --git a/plugins/auth-backend-module-okta-provider/CHANGELOG.md b/plugins/auth-backend-module-okta-provider/CHANGELOG.md index 7a5d7232f3..47bcb2f140 100644 --- a/plugins/auth-backend-module-okta-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-okta-provider/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-auth-backend-module-okta-provider +## 0.2.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.2.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 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 + ## 0.2.9 ### Patch Changes diff --git a/plugins/auth-backend-module-okta-provider/package.json b/plugins/auth-backend-module-okta-provider/package.json index 6bfa6ad27f..a4342efb03 100644 --- a/plugins/auth-backend-module-okta-provider/package.json +++ b/plugins/auth-backend-module-okta-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-okta-provider", - "version": "0.2.9", + "version": "0.2.10", "description": "The okta-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -37,7 +37,7 @@ "@backstage/backend-plugin-api": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "@davidzemon/passport-okta-oauth": "^0.0.7", - "express": "^4.18.2", + "express": "^4.22.0", "passport": "^0.7.0", "zod": "^3.22.4" }, diff --git a/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md b/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md index 4f2928f49e..9ad81ae8c4 100644 --- a/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-auth-backend-module-onelogin-provider +## 0.3.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.3.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 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 + ## 0.3.9 ### Patch Changes diff --git a/plugins/auth-backend-module-onelogin-provider/package.json b/plugins/auth-backend-module-onelogin-provider/package.json index ec55f461ee..aadfb3f273 100644 --- a/plugins/auth-backend-module-onelogin-provider/package.json +++ b/plugins/auth-backend-module-onelogin-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-onelogin-provider", - "version": "0.3.9", + "version": "0.3.10", "description": "The onelogin-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -36,7 +36,7 @@ "dependencies": { "@backstage/backend-plugin-api": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", - "express": "^4.18.2", + "express": "^4.22.0", "passport": "^0.7.0", "passport-onelogin-oauth": "^0.0.1", "zod": "^3.22.4" diff --git a/plugins/auth-backend-module-openshift-provider/CHANGELOG.md b/plugins/auth-backend-module-openshift-provider/CHANGELOG.md index 04c95f3936..9cf3f67cd9 100644 --- a/plugins/auth-backend-module-openshift-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-openshift-provider/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-auth-backend-module-openshift-provider +## 0.1.3 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.1.3-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + +## 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 + ## 0.1.2 ### Patch Changes diff --git a/plugins/auth-backend-module-openshift-provider/package.json b/plugins/auth-backend-module-openshift-provider/package.json index 072bd451c3..94c21f1718 100644 --- a/plugins/auth-backend-module-openshift-provider/package.json +++ b/plugins/auth-backend-module-openshift-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-openshift-provider", - "version": "0.1.2", + "version": "0.1.3", "description": "The OpenShift backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -47,7 +47,7 @@ "@backstage/cli": "workspace:^", "@backstage/config": "workspace:^", "@backstage/plugin-auth-backend": "workspace:^", - "express": "^4.18.2", + "express": "^4.22.0", "msw": "^2.7.3", "supertest": "^7.1.0" }, diff --git a/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md b/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md index e0ffc91aa2..5da6e92225 100644 --- a/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-auth-backend-module-pinniped-provider +## 0.3.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.3.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## 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 + ## 0.3.9 ### Patch Changes diff --git a/plugins/auth-backend-module-pinniped-provider/package.json b/plugins/auth-backend-module-pinniped-provider/package.json index e7f23d80a3..ba1e04ff76 100644 --- a/plugins/auth-backend-module-pinniped-provider/package.json +++ b/plugins/auth-backend-module-pinniped-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-pinniped-provider", - "version": "0.3.9", + "version": "0.3.10", "description": "The pinniped-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -46,7 +46,7 @@ "@backstage/cli": "workspace:^", "@backstage/plugin-auth-backend": "workspace:^", "cookie-parser": "^1.4.6", - "express": "^4.18.2", + "express": "^4.22.0", "express-session": "^1.17.3", "jose": "^5.0.0", "msw": "^1.3.0", diff --git a/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md b/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md index b3b354c593..431d709b37 100644 --- a/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-auth-backend-module-vmware-cloud-provider +## 0.5.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.5.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + +## 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 + ## 0.5.9 ### Patch Changes diff --git a/plugins/auth-backend-module-vmware-cloud-provider/package.json b/plugins/auth-backend-module-vmware-cloud-provider/package.json index 2bfda71843..0445438588 100644 --- a/plugins/auth-backend-module-vmware-cloud-provider/package.json +++ b/plugins/auth-backend-module-vmware-cloud-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-vmware-cloud-provider", - "version": "0.5.9", + "version": "0.5.10", "description": "The vmware-cloud-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index 75e9261efa..7eaef6e066 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,42 @@ # @backstage/plugin-auth-backend +## 0.25.7 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## 0.25.7-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## 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 + ## 0.25.6 ### Patch Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index a76637d74e..9db17b1a93 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.25.6", + "version": "0.25.7", "description": "A Backstage backend plugin that handles authentication", "backstage": { "role": "backend-plugin", @@ -54,7 +54,7 @@ "@google-cloud/firestore": "^7.0.0", "connect-session-knex": "^4.0.0", "cookie-parser": "^1.4.5", - "express": "^4.17.1", + "express": "^4.22.0", "express-promise-router": "^4.1.0", "express-session": "^1.17.1", "jose": "^5.0.0", diff --git a/plugins/auth-node/CHANGELOG.md b/plugins/auth-node/CHANGELOG.md index 7d0b33de3f..4ad613457b 100644 --- a/plugins/auth-node/CHANGELOG.md +++ b/plugins/auth-node/CHANGELOG.md @@ -1,5 +1,43 @@ # @backstage/plugin-auth-node +## 0.6.10 + +### Patch Changes + +- 2389358: remove leading dot in auth cookie cleanup call +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- b35f8b2: Fixed chunked cookie replacing edge case in OAuthCookieManager class where some of the old chunks would not get removed if new chunked cookie would have fewer chunks. +- e9dd634: fix flawed cookie removal logic with chunked tokens +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + +## 0.6.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- e9dd634: fix flawed cookie removal logic with chunked tokens +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 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 + ## 0.6.9 ### Patch Changes diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index c985217866..3e095caa91 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-node", - "version": "0.6.9", + "version": "0.6.10", "backstage": { "role": "node-library", "pluginId": "auth", @@ -46,7 +46,7 @@ "@backstage/types": "workspace:^", "@types/express": "^4.17.6", "@types/passport": "^1.0.3", - "express": "^4.17.1", + "express": "^4.22.0", "jose": "^5.0.0", "lodash": "^4.17.21", "passport": "^0.7.0", diff --git a/plugins/auth-node/src/oauth/OAuthCookieManager.ts b/plugins/auth-node/src/oauth/OAuthCookieManager.ts index b356f49397..fc376eae67 100644 --- a/plugins/auth-node/src/oauth/OAuthCookieManager.ts +++ b/plugins/auth-node/src/oauth/OAuthCookieManager.ts @@ -145,111 +145,154 @@ export class OAuthCookieManager { maxAge: number, origin?: string, pathSuffix: string = '', - ): Response { + ): void { const options = { maxAge, ...this.getConfig(origin, pathSuffix), }; const req = res.req; - let output = res; - if (val.length > MAX_COOKIE_SIZE_CHARACTERS) { - const nonChunkedFormatExists = !!req.cookies[name]; - if (nonChunkedFormatExists) { - output = output.cookie(name, '', this.getRemoveCookieOptions()); - } - const chunked = this.splitCookieToChunks(val, MAX_COOKIE_SIZE_CHARACTERS); - chunked.forEach((value, chunkNumber) => { - output = output.cookie( - OAuthCookieManager.getCookieChunkName(name, chunkNumber), - value, - options, - ); - }); - return output; - } - - const chunkedFormatExists = OAuthCookieManager.chunkedCookieExists( + const newCookieShouldBeChunked = val.length > MAX_COOKIE_SIZE_CHARACTERS; + const existingChunkCount = OAuthCookieManager.countExistingCookieChunks( req, name, ); - if (chunkedFormatExists) { - for (let chunkNumber = 0; ; chunkNumber++) { - const key = OAuthCookieManager.getCookieChunkName(name, chunkNumber); - const exists = !!req.cookies[key]; - if (!exists) { - break; - } - output = output.cookie(key, '', this.getRemoveCookieOptions()); - } - } + const chunkedFormatExists = existingChunkCount > 0; - // If using the default cookieConfigurer, delete old cookie with domain set to the callbackUrl's domain (legacy behavior) + // If using the default cookieConfigurer, delete old cookie with domain + // explicitly set to the callbackUrl's domain (legacy behavior) if (this.cookieConfigurer === defaultCookieConfigurer) { - const { hostname: domain } = new URL(this.options.callbackUrl); - output = output.cookie(name, '', { - ...this.getRemoveCookieOptions(), - domain: `.${domain}`, - }); + this.removeLegacyCookieWithDomain(res, name, existingChunkCount); } - return output.cookie(name, val, options); + if (chunkedFormatExists) { + this.removeChunkedCookie(res, name, existingChunkCount); + } + + if (newCookieShouldBeChunked) { + this.setChunkedCookie(req, res, name, val, options); + } else { + res.cookie(name, val, options); + } + } + + private removeLegacyCookieWithDomain( + res: Response, + name: string, + chunkCount: number, + ): void { + const { hostname: domain } = new URL(this.options.callbackUrl); + res.cookie(name, '', { + ...this.getRemoveCookieOptions(), + domain: domain, + }); + + this.removeChunkedCookie(res, name, chunkCount, { domain }); + } + + private setChunkedCookie( + req: Request, + res: Response, + name: string, + val: string, + options: CookieOptions, + ): void { + const nonChunkedFormatExists = !!req.cookies[name]; + if (nonChunkedFormatExists) { + res.cookie(name, '', this.getRemoveCookieOptions()); + } + const chunkedCookieArray = this.splitCookieToChunks( + val, + MAX_COOKIE_SIZE_CHARACTERS, + ); + chunkedCookieArray.forEach((chunkValue, chunkNumber) => { + res.cookie( + OAuthCookieManager.getCookieChunkName(name, chunkNumber), + chunkValue, + options, + ); + }); } private getCookie(req: Request, name: string): string | undefined { - const isChunked = OAuthCookieManager.chunkedCookieExists(req, name); + const existingChunkCount = OAuthCookieManager.countExistingCookieChunks( + req, + name, + ); + const isChunked = existingChunkCount > 0; if (isChunked) { - const chunks: string[] = []; - let chunkNumber = 0; - let chunk = - req.cookies[OAuthCookieManager.getCookieChunkName(name, chunkNumber)]; - while (chunk) { - chunks.push(chunk); - chunkNumber++; - chunk = - req.cookies[OAuthCookieManager.getCookieChunkName(name, chunkNumber)]; - } - return chunks.join(''); + return this.getChunkedCookie(req, name, existingChunkCount); } return req.cookies[name]; } - private removeCookie(res: Response, name: string, origin?: string): Response { - const req = res.req; - const options = this.getRemoveCookieOptions(origin); - const isChunked = OAuthCookieManager.chunkedCookieExists(req, name); - if (isChunked) { - const nonChunkedFormatExists = !!req.cookies[name]; - let output: Response = nonChunkedFormatExists - ? res.cookie(name, '', options) - : res; - for (let chunkNumber = 0; ; chunkNumber++) { - const key = OAuthCookieManager.getCookieChunkName(name, chunkNumber); - const exists = !!req.cookies[key]; - if (!exists) { - break; - } - output = output.cookie(key, '', options); - } - return output; + private getChunkedCookie( + req: Request, + name: string, + chunkCount: number, + ): string | undefined { + const chunkedCookieArray: string[] = []; + for (let chunkNumber = 0; chunkNumber < chunkCount; chunkNumber++) { + const chunk = + req.cookies[OAuthCookieManager.getCookieChunkName(name, chunkNumber)]; + chunkedCookieArray.push(chunk); + } + return chunkedCookieArray.join(''); + } + + private removeCookie(res: Response, name: string, origin?: string): void { + const req = res.req; + const existingChunkCount = OAuthCookieManager.countExistingCookieChunks( + req, + name, + ); + const chunkedFormatExists = existingChunkCount > 0; + const nonChunkedFormatExists = !!req.cookies[name]; + + if (nonChunkedFormatExists) { + res.cookie(name, '', this.getRemoveCookieOptions(origin)); + } + if (chunkedFormatExists) { + this.removeChunkedCookie(res, name, existingChunkCount, { + origin, + }); + } + } + + private removeChunkedCookie( + res: Response, + name: string, + chunkCount: number, + { domain, origin }: { domain?: string; origin?: string } = {}, + ): void { + for (let chunkNumber = 0; chunkNumber < chunkCount; chunkNumber++) { + const key = OAuthCookieManager.getCookieChunkName(name, chunkNumber); + const baseOptions = this.getRemoveCookieOptions(origin); + const options = domain ? { ...baseOptions, domain } : baseOptions; + res.cookie(key, '', options); } - return res.cookie(name, '', options); } private splitCookieToChunks(val: string, chunkSize: number): string[] { const numChunks = Math.ceil(val.length / chunkSize); - const chunks: string[] = Array(numChunks); + const chunkedCookieArray: string[] = Array(numChunks); let offset: number = 0; for (let i = 0; i < numChunks; i++) { - chunks[i] = val.substring(offset, offset + chunkSize); + chunkedCookieArray[i] = val.substring(offset, offset + chunkSize); offset += chunkSize; } - return chunks; + return chunkedCookieArray; } - private static chunkedCookieExists(req: Request, name: string): boolean { - return !!req.cookies[OAuthCookieManager.getCookieChunkName(name, 0)]; + private static countExistingCookieChunks(req: Request, name: string): number { + for (let chunkNumber = 0; ; chunkNumber++) { + const key = OAuthCookieManager.getCookieChunkName(name, chunkNumber); + const exists = !!req.cookies[key]; + if (!exists) { + return chunkNumber; + } + } } private static getCookieChunkName(name: string, chunkIndex: number): string { diff --git a/plugins/auth-node/src/oauth/createOAuthRouteHandlers.test.ts b/plugins/auth-node/src/oauth/createOAuthRouteHandlers.test.ts index a943717553..f2cb27dfa9 100644 --- a/plugins/auth-node/src/oauth/createOAuthRouteHandlers.test.ts +++ b/plugins/auth-node/src/oauth/createOAuthRouteHandlers.test.ts @@ -19,7 +19,11 @@ import express from 'express'; import request, { SuperAgentTest } from 'supertest'; import cookieParser from 'cookie-parser'; import PromiseRouter from 'express-promise-router'; -import { AuthProviderRouteHandlers, AuthResolverContext } from '../types'; +import { + AuthProviderRouteHandlers, + AuthResolverContext, + CookieConfigurer, +} from '../types'; import { createOAuthRouteHandlers } from './createOAuthRouteHandlers'; import { OAuthAuthenticator } from './types'; import { encodeOAuthState, OAuthState } from './state'; @@ -53,6 +57,9 @@ const mockSession = { const fiveKilobyteRefreshToken = 'tylmRqYlw3LrrXATyPerWfYMXrF86h3FeI5DECH8lZ6bERd3SsSFaJZ7EVYw0Rr8HMQVqJAurcSDZBtXjry3y9hXGRmugroDiZngNw8ROSPqcWzaNWDbaVuxCGf3jdjccOio7MnbrmMGpKUF8dfx8DhBH9Vogj5qCWpDajxnGpG0HEOcAHmQbsmJ0KHKVIggAtTYIefjvO2I75Us5VI0sId1GYU0E2AsRVEGedu6oexiLV6QgyJHSyKzTTRD7DqZ4ktVLDsjOBUhAAEWbAl0vxhvjSUEt4YYEFshV5T13MhRGGma8QtVC7R1NItwtojj94QGfwnnQEviIuECONwQc6b4ObeQkPn4bgbsWG9PD5UJA9kBycBV8SqBQkKvT5nsrlsnO4E8zBJBtc2Pd14MUh6CzFng3Wee7v5uPQAnyDJT4V7COwY2F2opz2ifau6c6gUT5ybkEKbp945mo2R0mu99C7z99jhUq9RRxgrtSeNQ7o7j4NksnJThZMjxvpi978bG2P2oMWIl59LgsrYyUt2bkjEB5sQ1qwngitAQDy57flLwAyHAwTobQWjGDUumAAOHe9UqjsuPd6qf23nD0b2nn1rCtnGyJ0H6luaTT0Lqet1Eq9XRLHkL52mJN4iPLWKcDrYsK6KqKhIlVkHa4zXRKHnKONOgMqYioC155yncAirYYJQl142MWamHXqW3LsIjrPwPh9xj02TsWMG4hDt2kVb8Rp3qGJTDyDM79NKbIFFSkATIwyQmQB1THo0kAkpu3V7YYoOfDSl4N5TUbPJRSbEug6Xg3dqMjaHrL729xrGsc0iWC8DAKlTzPKnoVcjaYeru6zHIXhJJcs5BIxeA6afUcataOrzTddXdBiCehqtzjS0omXdeiHKv5d73fLeC5luUo1um6eVEidr5fXEApGpSHKsEo7mv1a9xOTCZPCE1lHntIkaG2vTgEc14QmGTXAjnaxuThGROxmm2gX8xM3JD6HYAfTxQpD5Y6fcMyF5joffBjLdFvHyYC66BghXIb4M7oZ0QQNqzMKzNuJ06JYLK4e0Pi2FQHt1XcO38sknlFnQh5S0GPMiGHXStIyDTfZNwPnI7iKH0GtsRZBUPmoG2cgJWSJPTMZOnIaC5AzZ8AClMT58GAa3MiyIhVLAs1hBv4BMu5mbY1QVSZ8UETZTDnXEn6rGIES41zkr1A3FThUhUTzJAwYLaG2nYn5Dtpge5C1B4LGElcVQDLJKKV3e6foYQMHcIzlwySKWSylI2bRjGJtE9594rlB9Yz1eno7KTtxy9IrMoAd13KnOCnrKjL40kGEWwbDScT0zob04qw1uuIcldUiLYLhPD0MtdxqtZTgvKeVTCeFNo23kWRnBvwhLlguGqvPCMfwXFjLldJsei9MElZgRrPgubBb7ZjSSdGT6CKey6TaFPx5jOT7V9v88jfQJMyEoX9jMCvCQZMFZyCMQdGKU40dOaozRqsymNMhPOgvheIhlXfN0MhU6RLJGhUen0QLuRBy2MiK82z1nkKSnlhMB3REmFmAIXrVhXiOalPLDVB9kR3csn5f6bddAcTzDLrv2YH6ZGhmXwILKJ0osKm8e9aIKVWHitr0LXl5zvkDj7U9CIVtktLLRodLTVxlKRS048LmbRBpGafoxXgOlcVfDmQO8LdKbKHWJOEN4oEhxF3zEgPf9rRMWEI0oE9KoOau7R5DVJMB6Bbf6tOxbHlVwPmnXGsEIJFDt7Wb79knZihK1mfLqaOumcAznQZiFdNt5NbBSestUCytXDDJn1fm8IGxXsBQSsEEtklg9phenjmMG38ABmyLhZCDiDEQ4M6VRPseVXpPhNYuija8YePhrB1y1aW1Cab7wglIIbeGj1Z06sJ6i4HGkAXtn2M1HolRLc6oPohihPaYtrKOFCtJarwbCHMtQjaR9GnzVfGoLFKemhY0kOZvAdLmB0g1QUKsmcKtNdpQwVYwciJE9vvIlgMIpeWBrU6cDkdIbQDOM6dvLicO3BZQqBXbSNgLcXsF1JqlD5YgUzdARjxv7tagxjXVJ4DVB31UgXcBWlEmI1gGrbVZUTQ4Kaj609StfQw2XNNdal8eES46C4rqHtTGCtdUjW7QINXaqt1efjNk4WYFS9OTGKL0GcgvF5ERlHuAQh0R2R0rSQLl72ayOZDeausNRBDYyI24pie4gFb2LI1hJjRwVTJej0xllYoPtMgLxmkCnUpPhbpPjIwPYIZfSYE6CoV7oxy4BDSyK6ueE6dUak6hlEZwOnDh3aOTSVioq53vuqk6ofC2kT2ar1PfgH5SGxpXB6RpI5bYiF4NYoDX9zEOKHD7hwVHoLK4UusPJWSNdbIDuIBmGb6p83vnEZukNaY5ocPZTZyM1Ex8dDOFiOiZs4bOgOY0NsJKv8pb4I1lDzSeBqPbytFkSAQrU5pUgK45bSIFlyEG3ef59nPHblCA8GstcMSm3zZETd5yVq1NAmQvAnabHyc10T3Arp8cm9Xe1SPVGMzEP6QjXMkZMRbwn8k9nXXcFNfRe6XjE4EhifwRuAYoUJ5jQEF5yrF1nYwwbsx6nlKtxlkxWljavjxH1aP8w8t50T3THX1hx39aktWddjRSjQJtu1rZcCxBCBx1FxictyrP9y5dhVVMGIup6mJ8vHws8nudLqHaiEEQlGlRGKi6hMcF31S8l1RWG6KJRsbm8x6jvJchk5ekr9Cj2fmCkpVdE2yCianjU7pCLibtfR7HV36uGK2d0DXwbrv0sXPr9M7KGgTLmSjlUJrohDBHshfHPdT5DgIq1boeBBZjxlxNoUxN7veZ80VjFSuNbCxTg9rcZeAT35qgt06oHboySGL1ZwGPs4Ip73tN1AYRlfo3PZBHWCT4V8mF0R9DDyFEdhkZtvjJLTNqroJsUuUAQzggheYufFunJ8lrwJyche4XaJqnKkv2JktuSbLXol2NoNX31493O2F2nnwWbzgff1jh2gDvPiG9o8wvNrXY06Ar47WDF62YipcLuAY50RnPWaVOgC2vV0OmDGMxnu3niKM0rSiyOtwWzc8SbRPkdRzGwqmtmSFfJSLzNdDLFAbGyDel7WzMUtWuXfvqEZwLVco49zrlcnLPAT4OKNvSl6AcZaTulU9sA2xbEK7gKMv7r8HNJLdEze2cgHG6rXUiayefWE3o2V8YjSHyUwV4PmrW8neAv0v5zKnKBsE6QwlYX23NoaPBvSokjStkNbLxLec0ip6Rgy7vJv5AEA7nxbDM53WUz44898OKHrVjN9vqvoKPBQUXf5BHihloJQCnpk03yzcc2h6y3e9rMFPyMyLFI6Hd4jCOuwtOqTJjAJAvcJzP7gKkeAe9N5ObwtjNqkkWPOd1AJccZukWnT1Lu390xfY3eyRjdMutc5OkTo4di2u0AjVH3LtRIRfa0AHVEly3ZvBCCYo9jCZ0CV8s6PlNOJ6SkyZyZzO3VyOUYxfj09D0P8kCaQ8YcyyoeSKPKVlFvxEqALW8nXASnKCv5mvsAyZMGpYRHtoyu4mVyCIrdOjmyGIK462aO6KaGC6lfWkKisQhLmSK2PmrPeimdS5ViOHllBfwexF5AsmNi6LYrjS00uPTki9K06h6yuppMVV8ykV6HIZoLABicTm5NofudhYrqV5hWstXxunHAk2cNq5Is1mFSNU4eedYbqu2c3y8iD3QzLWO6FmxwWFu3XUK8upSJ2cK34uUl8kX1uRsWSNEMIMOils8HZwHtxZ9fZ09sT2fRJKAIKxx57h0leLsjciNT5iTrMHgsNTlpa5I1QDSbEqtlFhDvh3TYmAYBYUENhcVBZmVK62XhTPpzgmEIQX4dlZJbz9g6I85jjfJILKLsEpmErtbzglmQpyQNw2EQFzYIWu25tv2dTDoDRP7uDV4KDKl5pXKRwu5z4UQI4sXLPACK78x6EoEyVUFnBktvnT7mQe5bqAlJ7dhqUEbzC3AJIgVosnRplZqXInSlNdpTFOasW3YyQmQtDB4183vKzCe4UMdKnHkRAoA127iSIUvKe5w66Cv8cQU38AFyWp5eedMNu9NlbqepUEaYSTmas5odjQUJ23D35QCuO67zFILoDKqWYv7jVKXMIVZUnyvspAWbtUFYDij5SmP2QQCgCpExeDtmoVPdjusL1QqQjukGVc9nXHwjhZ7KwrtrC5XMeKgl9EBvNjRum0sn4B7MLhuqJPqreoTLE2unGcI34mKbWRbyHjSq5xi8uZy72MuDCXyUU4GmZrXNqMG7QAw4wAvMKI31G0uHJEaOSV5II9XGAuYSGTuUuO4y31NTQBPpa2O0JFfnFJgYveVoWL3cCcjJivvEWezutrJ3eObOFbV4ACiZ1uXwJFlI484ILeWSNkS3jaSgfIYvBK9SMVZwg6IuFtjX6D855I4U4XHMqKWla3IcaKvIKwYFlrOWPq81lflKMPLQvJnrEiUi67jZJZYF0rMKtrv2ayFEfFBDtG58hA9uIkCbvHAQOBeWpzuTNUdtZoNdAuLqxddjpxZr4moFtk1CjdQihJNQLPeZR0ZrQYVBUPMyFIV639RykPOPyhxFMSKQNo9xjNCuxnoZWBbtqHHXtFZdUBq7qkb0GzfiADZ7LaRtj9MNOLLej35Wy0L0xPu5Hy5v9N0Jslj65YZJvC7N8uMYuIR6rMuh2MhuthHOlxLLbzcBy7gicP5aFofdt4fkOBgitm8CCRdm0f9DQpETZ5hEGmkI4V59HRs2Up4CG2ajOFIZwRmbvs3B8M5NZmwf1mmek0j7qfjkOhpsp7RHMqUztEYGd1RX3S6vaHDSFEouqPiU07CjNFDV1f8x94tUq8ldicDtQN1DbR5oBTvdmQZsgG8K6oU70IKNhwX4idMFNaTcQ0ZsfJk1rQLh6cIoksmrbm6tYu97HQhVsDmavCs5CnkN2mZDEEj9HfK6O53ck273X3jRo01lFfT65KcUSH71zr3KkLPzxBbhhCRmdpXQSfyHwbN7QbKWi6NkU8G7xb8oRFFsR'; +const eightAndAHalfKilobyteRefreshToken = + 'Ad62a2Bfa5cdCdE489CaF47BDBaF9fbd8CCB6b866Bfc04bB2f8ec7D19ddc7d3189e5249e231cAd567745d4Dc83Be5ADd5BdDdC3cdDa8Cbfe2B2f2Cb01876eadF0A444eA420FEfb5EaFD918A9BbfabD8eaa512FdE7F3fC03bA134d99ebb43fe58eE8C7DaA587a563eac5B75E4bc03E31BcD9dB5DABa0a24e7A6B44C0C585Fc2dAA1dCc5ba110BBeeF7404dE5a0e416A86e23aFADd9efA19faabC74DfdbeCe70E396E054fe2dA1dcCddf5f6f36d1dEF8371FD31BbCA9D1e23a6deCfC43Db00750a2feE0fc7ff6dBdeBaC1557ccA546E8B0B2E6860cfdDC96bBf2356Adff7CFCB1AACC183Fc3DE8bEb0dcFb22D19f44B9f4a6fC0AbE9BCa64e31baaD85aEdE42C1E3b4Cd7F8FEEDDda1dC25C50E93353Df12dCACCA85cD7E6Fb99cbAB4Fb59EDAc3De414ffdC1AbBa3AA06Cd11e9c6eAA1Eb6B1dACa1BE7afd34234Ae6B1Fd2e2Aeb7e0C74cbB3F6fFc39ec6Dd805ddA1433cDfD4bCBB8b19Aa6eB2d19eDE0e57E668302b9E700a4f170967E38F0C8c664AcFbAE6f98eAB5e3EC96AcFcf862Ae90843ffdeEBB89DE2933e55a4bCEB45E07aB1AeB2e8dE4e70e7d4aDB8B3020fC48d0FD4A2A03c6Cd97cbc6A169e13b7AE73A37F25082B4aBe19EB050F0768e2Db7D3514BCA067B79687d03260bd0A4bfFe02cF38Fd31bC09D52fccFAD1D73E51BE55f11BECfFBA6F988fEE16004Be54AD6CC87741DaA40Db1f600fEffC0fa17b77F85e883E6F9BE8e92dA6C862C28DBf128de0A8d307Ad6b1ddd4a9493ee1F8731aBd8Da2f0e85Af89DEC18678bd7ffAA75FA5a79f4cF5A0AAb4f3aD1Df7E2BBDD24Dd3BB12f26cA5f0359a9462f5f5e32EBCA7346BF171ba4F1E2af7f3Ee8B3C8C776B4f0D0622EeBDdBa9Cf9314a85c9F2952aaf25bB2B79A2418ACa6f3bbA76eFB99FA0eC8e30F4e7b5853dfB8f6cdAeEb337dbeD2392e95BECF0Aef66fDbeb0803Bc950EEfed4Ff9b865C80bBBdb074facCE5614E8CC6DF0033cF223c2D5dC60Bd6Bc30141dc5120B8D5D429D4e07cAdfBD67BfdAEFEce8D32C15BA9CaDC1dCCDbf2D59CB370B57cDCa083cC9fEabc8B607c74E167cc1AAE121c6265Fee7b39eDFE10d31E8d59ad4e266a176010AC505BEd8ca057Eb224cf57DA4dfb98fd10A3Ee64F6950Ac54dbD3ffcbB35EfC84BEA9cbCa49872eEc9dAb29161d7b80475C42226914B5FaFCecC023c1AecEa9F89Da95bA4EF2Df2c3Daf14cBdBb9533E07E2E6141e76d1558dF33aeFbfe572fcAd33d3cFCC055bDDd8D8d8f1dCAe6Cff2aCCAfe653b7e845ae459563D0da4E995D35559200EF6FaDbDA59a6BBD323cE8E370fC3996Ed9ebcef1dCeABb067c2c9E96adFC22F058bDEb00bc037549CBCEAF3e6D3b0bB914Aa8CB00a4a97D34cc6D15Eecb295B0a26bD83109EF2af3C82AF55f146ffB26Ebe727eD86dDEB1BF7b714Bb0a58Ad152daAaDC7F5dAEDfAA109d6fCe2847D3b32B41E8c29DAC6499bDA10F8A7a5c3E0b2a1d0f2DE2c3e88de4DFDF85B4AD4Ddae8e8D6dF5bF2affd8dFc2185A92E81E9CEFB0cd71Bf5Ac4C09AEe012eFC79B5dFf447b9AAAFadA871AEac9be09cDa26E1c56F54DD8b8Afac765Fc452c0e11B45daC6ec766B80002C544E4e6342b0B445CE1AfEC0FE8E9d7DE5EF5Ec4921515Ddb8Ee7BEbd68d7065154252E75d922A4b8E4d0AaEcc6Dc3db498E9CfF41bb121B69Ebb774B3cf998316C80Fe73F7d688bcDec894BD3adf5c5AA6A36044C5B01D2dCCcc23BF21F91cd95E55ca1aDaA770AE0b65fE67aA95d44D0BBD3FFDa20bc6EdFcC7e2ef5B46225EFF5c767BdCE8C666Cdcae2aCf9ADb7CD3C10b59be627AbE5B0FDEA16dD9C4727FbECF6bEcAb1b8E1f5716eDEbEaf4E550B8c7CAfDd0C0BE15b6D4bBE3261f9c657aB885B8862C74ED9af0FD725CBCBd7ded2B0d6fb8eEdc802BB1EC2c7609399844a2DCaDC0DF2a0ada1fa91E7a4FFeee6C7AEB3A42DCb85eCc3fe6b31B8D857225a43C49CDBAcB9E855CF9C3ecC05cc41FCCdc9c6b10aa201D0471658aaDBB2077abc0516ee7911F8e1A435eEDBf50e0C12b5AB3e40A4DC2aD61Cdefd6CDd2d16dC9c0d5eE5C2573A6AfceB43d9E6f9e3C7BdAa5CbD2FFcB1AcFAdf52B6EC5cc5B0AC36c2c932124dc8C43cd6867fBfAc1F4DFF957E0f8F09Ea0e4C60faccc4D4E84bC0F0163aD1e2E6977ff83FAaebDd2cc1Fa8BCa821B29f34F242E3FdAb05Bde16a0C46af8CeeAC2EaC00Cf81C112e6Ee0b79b9f5Af9eD0acCc330794aeDf8aa3551C35063BF2D108E4a190cDE9055a3eFB08CFCCe7750eCec6eD4BF2dFDbec7b3A9Bb6cDa71eAb0De2CCA3F6bACFF4ce9304b7B5EAd78a5DfDef6cDbd2df49f0F806ECd85Dab8B1B9B76C2AA0d50baBCAd9db11761f572feF3D50CEFAb6Bf3e38Af8EED4cafe43E47BD68dB67fEfaDC7b3FF2eF5C285DF0850bE8b17B2AfDCDa0923b4BF1f0E02a279B0bc87c5d2Ca60ADc1ccccE07f58BeDC8a31bC9F25bF3D7C6fA4be472cfA4B1F0Ffd37d0EcACAa1Cf1dA07Ee8aE5AB9f88E54db4b6be0cEb3aBcb0DdEf5394a40d25e5138aeBBE1f3b191eCcBFcCC949AAd584BFe2Fb932797E6ABBCaB8BEBB96daBD097fedA5fE1dfce3F0E4BEc518aB25FfCBBb0aA007ffafb04d3BbCC5e0347AcBbc3006C05C41CBeeBae17A149b15Bc1533aeB44017cc5BCDfc6c5d2AcCEc4c17A44e53D42dc68C6f93eFa7D1d2f9f2D4e4Dddd90dCa0f15dD1aa40D7eBff9b0Cb7d7Da33FD2cb0d5Af8DbcbaE47Fb86Abf8bE20A7FeBBD4a390abB6abfD8C8D5E41e2BEd8E3eBFD2d1B8d41d605AdEF2BE63E22C09A55fBbAbfbeb8fed339119bf34a8d0BA3e912702b9c0DF7b476FC538eD06571EF2dE208beCDA8134BeEcb1F794BeaaDCd5619fd05EABAA82d58cB0cC9Cc3adA0D5fcF915b6E9dA9517caB500aDEAc8aAB6ad26b3E2F8Bf7cd2004f7cF74cA4b2a495CDed2a7a5CE42b6DC8e95326a7637f21EbFCbEcE8860EfDb31Df9aaF6F133674723f716a600B1c01DC98c7aA4fBe9cb81E7C50F3Bdc966D8BDa1AFcd4EBfB922f2eba045B3726d3Cec02Bdd606ae43aaeaa0EA945D4ec95d9E5E1AcF92aa75F4db4AC453Ccc51ceAdBD7b0614BfeCC303AADc40bCfDDfC76bfafFdbcD47D4B729EEF1CCED07C3dF85EEfC2eCcbbB45F8D5f1D09e9A1964fEF402E29AE024dEEeAeDEfF377E7b2ee2F621165dEB3CE9f4471DEeb396Dc3B508Bb0C2A51c4CC4Ba5Ca1aa9ca7b401D6C0421EDB83CF03EA16bA24ceE3FC4e7Ad4BFc7dAbe16e4C2A51F4e2cAeb7bC7287684268ea7FAae360A1A8aF5BFED450db3D66363284eD57B2f9D25A5c4FE424a7feE764eaA0e47F96b77Af2bd725c1c8DbE5A7FF2e0c7cE7F9eE0FCf7bdF00543a9ED1C485CDC2e82386F440D04FEa6380B9bbE48C605bF0fbDe0dc2cEAe35C2BFB84dF62185B8e6BABBE89DcCF1b1Cedf5aDadeBdd3Bd66cc76aC6badb2F1ec27C411ccd3cCD823feE8A8F9aE8C0e1cc0fE6484fBd2c58C77Fbcbdc662C7EEeb684dAaeE0489C40d8A91d6DCec15A3e506783C35acBD0Dad364AeeC295d7eD1BecdB2d1d9d1352dE4F7f52B50e272ab9E57e8AFbCC54fEEbB640F5FD37b18f6a46D70f0cBCB98aBAdd6EfcF06f5f5Aa6D61b3FD4c1aeB46Be0Da0Dfcaf5c45e2FDF46C43aFcCb952Be929adaFcFeECbCDB6Edc61472DBC37Eeeff5a5B64Bd1Fee6B5e6861EcBfff3d6103a7E3e361EC08Ecb4d81c0FdefAdA9d861c12e7aD85678a23E5E94F3dC9677d4B82Dee5A24dDbebF20B880c25C6955dCCC276ff9C3505a85bBE6aB08E6EFb3C35DC1fcFDC8F86E4bE7C1F0fBF8b1B8594F122fFAd7c6CDe8fba97af1697Ae3e5cFc485B8E0Ae66dbD485afceBAAe550cdc9E4B4CbEbC51c85D6BeCb83BF51f41DDeCAbe4bB75209eA0EaeEe6010Da909b5CBd1D1CBaE7b579F48cd5Af0d2F81c1E8ADcaA4DaCA01Ea206fee4ECBc8fddaa505b41c114EABF753F5aD02B25Be96bEa1dfF5e04EddEcEbA8881EfCe9AB957e0366DdCD9E32ecBdFC1B0Ab6f1effdcb0b1A3d2f7eFEf59EB2bddC130305d4bAe6f627e1087B3Ef5Cdd1f2A75Eadc6B4d79A5b6d55fEA7ea30d3cDd74D74F3bD2aa99b5ab3FC83Acbb9ee0fBa05D4d23DACeBb16cda8A2bF7Ca4D85eee1BAc9D06Cd899861bB5e2e4B9c30E1C887d19DbF81cFe8Ae9b777249f542faAFE3b3bd49Ba5295afbd927A475C0C3B81C1ee6eEB8c00451CFBcCFfF7Fc8aD0b7251c00D507cEfe86BeEeFe5FBded0FBe30CC447DFaB930c04c4460e9f9cbf1BBF2eBFdF06D3AAed2af2BEDBA0f296AEfAf90571F9B63e0876fFADfDf61408B6dedeBfE2dE7a8ee750Ef78B1289b6fCdfdaC58393e373a5dA35d2b149c9FF5a1fA9DFeCc37c678Ce657A172D5d990BbbFDa089288dCa5F89f6CeF7cf6b1EB2609BBD65e6fFfa815Cc59abcE387ead000Af1B1C1cC87faB990D1b9AfcEfDc3D68cCCb9b7475803829dDebA48e92df26AC70027CcEfB6F6FDd1DA25DFB9dA0aE50398998E7EbEEaE11BB68a2E03c5f8306eaca1aF15CeBCB05a6FDb8E3BdAAeFfeC21Db12762dDBABd3eDf12A438B3C37B51cCaCD1Eda73dFCCeF06BCFbECE21F95e2Ae1d0F161dEec60a17D01d1Dc425b85aCD0adeCCBdddd310581aa0eF48FfF8a6A727E95BADefF6Cd011Df1c993DB5aE2ec8c3dD42cD704a5e0AC8bCbae34F245e7ABfba828efa3adFFefb33fCa32ad0b2Bd7C4Ae56C3d932E7A1a24bE87Bd235067A73CcBC5A8Cd44B0Fa5131dacCDEA7454aAb7c0Dfb006B6EbFAd3dD1EAEee79c9Da23FA02D88CD8333BE964E7D2eBbd1AAAE06E67463CCb6eceA604Cdd2A16d834599c86914Ea9b063243C4Ebe8E6aa677fdE2b67A6eE4Cfe1801273137ec777DEfc84D4B3d0e96dFF1f7CCaB0CFD307Ba1fC16b9C4f4d1fee0ee0f05BB1102eEeA5E49e7f339BE6d9335Bf2F28d57f194D9facA9c5af5B08B08D7530c7DDe99D0Ae0D8dD5d0c13B77fDf1E3Bc0aA8e71C32EAD5AaD7Bed538F26d4deaA3A67C667201C5Fd62A5FaeDde62E6DC78E62bEeaBFaa030cFDa4E86f5B3C48bBE7bB4Be5528D3D0fd69BCCE1D8B6d04Ee6B9a1B5c49572B21FFFf1bDf06cd98fa9ED5fd9Dc3B04bD4530B8CBD5d0106799E4CBCE64c65EFcfb4f84Ba0ced14BECdcd4bb33CA38B4eea4bF7e665Eda4a8ADfBfae5C45b2B91e2C1eCD0CcBC85275d4Ca8318638eB77Eab97845c40Ccd34f7ef99385Ce275B30013f7BfC2c088a2F332DAF6Ca2CFB21d5f8EAF177B519cAF56ca42a9DAfadBAECb5E16Bf671BDB2BBb15c8eb1F4Cb9dfDbfbcFcFAEABAD3aeeCF8e84BbEECf3dDf0f7FdBCF44aa1A4ac7bFac026B8Ab6C0004a1dCc6bc2b5eb8fA7cEEe9cEeAce5d287BCE0E5f3bBffdFaED134286794D704b32DD36cF2FF80dd39FB09cAbCF1FaF07a2aFB0EA7FeA464b90f77090B846Fad58Ca0fB83eA3d6C4dDBCFd17b8c5EFCc7a6e1efDaaE8B4Ecae65C89BC80Df9bC844cE9aDCcFdaE2be0f0Bc6AdBDB6E814d2050febEB3BaFCfBCCADF749BC2Cde2BeC0e7E6e14afa92Fe5fd51A8Ef62aC53FAE304B0f4317Ab5db11fD13ecAcd8995E6F87Fe4Ff5FF89e4B7b44A754f52a7E4fDDCdfDDefEE4aD7AcAacDCa17fd3a2b43D4bc74B3Cad0aeee8eDBa25D56e3Ba0C5cd8B8E2a2c0B8f951Fd7AFC75edEAee0c28bf3cEBc3af7C60ca147fC9cF0886B066e263f36CA5B5BB36aB82f0f0A58FE3d67f1a9fc24270e6170ac34E6eE5c1bc64B1B5f416afbb8E8edD2cFA9fdcAF7Be904EBaA1abF8b0c2D5cc176C5866fcbCECa5342d7439Cc5E859dBe6a18e9c58EC2e9a5bc20a79D3E3CF073aD4c4F12409E787fFA0Dd1C6d3f01b3CeaDC7F5Cc9be3415f0DFFD5F2E2Fb0dBA6843CCB1aECbF1480E8fCFc16e2cEDB8B8e292dFFeF1bBc6F9C7DD4b873DBbbD528d0fB1E4D02ba3EDe9f1c13b4DEE0FC8BAAdc5cAB40AF2EC4eFDAAfD31104bC04CEFFCbEAae6aA1C91FAccAEed7B3dE6FF226Db01f62E239fba8BD5aE9B7C9a4DeeF746ec2F9580368FB1DDcF785e410aec4a70005a609CBBebf42f714b3c59a76cB06c3c86Ca3dfbB5EcAC'; + const baseConfig = { authenticator: mockAuthenticator, appUrl: 'http://127.0.0.1', @@ -116,6 +123,30 @@ function getRefreshTokenCookie(test: SuperAgentTest, chunkNumber?: number) { }); } +function confirmRefreshTokenCookieDeletion( + res: request.Response, + { + domain = false, + path = '/my-provider', + chunkNumber, + }: { domain?: string | false; path?: string; chunkNumber?: number } = {}, +): boolean { + const setCookieHeaders = [res.get('Set-Cookie') ?? []].flat(); + const cookieName = + chunkNumber !== undefined + ? `my-provider-refresh-token-${chunkNumber}` + : 'my-provider-refresh-token'; + return setCookieHeaders.some( + cookie => + cookie.includes(`${cookieName}=;`) && + cookie.includes('Max-Age=0') && + (domain + ? cookie.includes(`Domain=${domain}`) + : !cookie.includes('Domain=')) && + (path ? cookie.includes(`Path=${path}`) : true), + ); +} + function getGrantedScopesCookie(test: SuperAgentTest) { return test.jar.getCookie('my-provider-granted-scope', { domain: '127.0.0.1', @@ -125,6 +156,12 @@ function getGrantedScopesCookie(test: SuperAgentTest) { }); } +function extractTokenStringSlice(tokenString: string, index: number): string { + const chunkSize = 4000; + const start = index * chunkSize; + return tokenString.slice(start, start + chunkSize); +} + describe('createOAuthRouteHandlers', () => { afterEach(() => jest.clearAllMocks()); @@ -329,14 +366,301 @@ describe('createOAuthRouteHandlers', () => { }); expect(getRefreshTokenCookie(agent, 0).value).toBe( - fiveKilobyteRefreshToken.slice(0, 4000), + extractTokenStringSlice(fiveKilobyteRefreshToken, 0), ); expect(getRefreshTokenCookie(agent, 1).value).toBe( - fiveKilobyteRefreshToken.slice(4000), + extractTokenStringSlice(fiveKilobyteRefreshToken, 1), ); expect(getGrantedScopesCookie(agent)).toBeUndefined(); }); + it('should replace and clean up chunked token bigger than 4KB', async () => { + // Create and use a custom cookie configurer to make sure to circumvent legacy cookie removal mechanism logic + const customCookieConfigurer: CookieConfigurer = ({ + callbackUrl, + providerId, + }) => { + const { pathname } = new URL(callbackUrl); + const path = pathname.endsWith(`${providerId}/handler/frame`) + ? pathname.slice(0, -'/handler/frame'.length) + : `${pathname}/${providerId}`; + return { + path: path, + secure: false, + sameSite: 'lax', + }; + }; + const agent = request.agent( + wrapInApp( + createOAuthRouteHandlers({ + ...baseConfig, + cookieConfigurer: customCookieConfigurer, + }), + ), + ); + + agent.jar.setCookie( + 'my-provider-nonce=123', + '127.0.0.1', + '/my-provider/handler', + ); + + mockAuthenticator.authenticate.mockResolvedValue({ + fullProfile: { id: 'id' } as PassportProfile, + session: { + ...mockSession, + refreshToken: fiveKilobyteRefreshToken, + }, + }); + + // Simulate 8.5KB token (3-chunks) that should be replaced with 5KB token (2-chunks) + agent.jar.setCookie( + `my-provider-refresh-token-0=${extractTokenStringSlice( + eightAndAHalfKilobyteRefreshToken, + 0, + )}`, + '127.0.0.1', + '/my-provider', + ); + agent.jar.setCookie( + `my-provider-refresh-token-1=${extractTokenStringSlice( + eightAndAHalfKilobyteRefreshToken, + 1, + )}`, + '127.0.0.1', + '/my-provider', + ); + agent.jar.setCookie( + `my-provider-refresh-token-2=${extractTokenStringSlice( + eightAndAHalfKilobyteRefreshToken, + 2, + )}`, + '127.0.0.1', + '/my-provider', + ); + + const res = await agent.get('/my-provider/handler/frame').query({ + state: encodeOAuthState({ + env: 'development', + nonce: '123', + } as OAuthState), + }); + + expect(mockAuthenticator.authenticate).toHaveBeenCalledWith( + { req: expect.anything() }, + { ctx: 'authenticator' }, + ); + + expect(res.status).toBe(200); + expect(parseWebMessageResponse(res.text).response).toEqual({ + type: 'authorization_response', + response: { + profile: {}, + providerInfo: { + accessToken: 'access-token', + expiresInSeconds: 3, + idToken: 'id-token', + scope: 'my-scope', + }, + }, + }); + + expect(getRefreshTokenCookie(agent, 0).value).toBe( + extractTokenStringSlice(fiveKilobyteRefreshToken, 0), + ); + expect(getRefreshTokenCookie(agent, 1).value).toBe( + extractTokenStringSlice(fiveKilobyteRefreshToken, 1), + ); + // verify that the old chunked cookie 2 is cleaned up + expect(getRefreshTokenCookie(agent, 2)).toBeUndefined(); + + // verify that deletion was ordered for old chunked cookies + expect( + confirmRefreshTokenCookieDeletion(res, { + chunkNumber: 0, + domain: false, + }), + ).toBe(true); + expect( + confirmRefreshTokenCookieDeletion(res, { + chunkNumber: 1, + domain: false, + }), + ).toBe(true); + expect( + confirmRefreshTokenCookieDeletion(res, { + chunkNumber: 2, + domain: false, + }), + ).toBe(true); + + expect(getGrantedScopesCookie(agent)).toBeUndefined(); + }); + + it('should clean up old cookies (non-chunked) with domain attribute during migration', async () => { + const agent = request.agent( + wrapInApp(createOAuthRouteHandlers(baseConfig)), + ); + + agent.jar.setCookie( + 'my-provider-nonce=123', + '127.0.0.1', + '/my-provider/handler', + ); + + agent.jar.setCookie( + 'my-provider-refresh-token=old-refresh-token; Domain=127.0.0.1', + '127.0.0.1', + '/my-provider', + ); + + mockAuthenticator.authenticate.mockResolvedValue({ + fullProfile: { id: 'id' } as PassportProfile, + session: mockSession, + }); + + const res = await agent.get('/my-provider/handler/frame').query({ + state: encodeOAuthState({ + env: 'development', + nonce: '123', + } as OAuthState), + }); + + expect(res.status).toBe(200); + expect( + confirmRefreshTokenCookieDeletion(res, { domain: '127.0.0.1' }), + ).toBe(true); + + expect(getRefreshTokenCookie(agent).value).toBe('refresh-token'); + }); + + it('should clean up old chunked cookies with domain attribute during migration', async () => { + const agent = request.agent( + wrapInApp(createOAuthRouteHandlers(baseConfig)), + ); + + agent.jar.setCookie( + 'my-provider-nonce=123', + '127.0.0.1', + '/my-provider/handler', + ); + + // Simulate old chunked cookies with domain attribute (legacy format) + agent.jar.setCookie( + `my-provider-refresh-token-0=${extractTokenStringSlice( + fiveKilobyteRefreshToken, + 0, + )}`, + '127.0.0.1', + '/my-provider', + ); + agent.jar.setCookie( + `my-provider-refresh-token-1=${extractTokenStringSlice( + fiveKilobyteRefreshToken, + 1, + )}`, + '127.0.0.1', + '/my-provider', + ); + + mockAuthenticator.authenticate.mockResolvedValue({ + fullProfile: { id: 'id' } as PassportProfile, + session: { + ...mockSession, + refreshToken: fiveKilobyteRefreshToken, + }, + }); + + const res = await agent.get('/my-provider/handler/frame').query({ + state: encodeOAuthState({ + env: 'development', + nonce: '123', + } as OAuthState), + }); + + expect(res.status).toBe(200); + + expect( + confirmRefreshTokenCookieDeletion(res, { + chunkNumber: 0, + domain: '127.0.0.1', + }), + ).toBe(true); + expect( + confirmRefreshTokenCookieDeletion(res, { + chunkNumber: 1, + domain: '127.0.0.1', + }), + ).toBe(true); + + expect(getRefreshTokenCookie(agent, 0).value).toBe( + extractTokenStringSlice(fiveKilobyteRefreshToken, 0), + ); + expect(getRefreshTokenCookie(agent, 1).value).toBe( + extractTokenStringSlice(fiveKilobyteRefreshToken, 1), + ); + }); + + it('should clean up old chunked cookies with domain when migrating to non-chunked', async () => { + const agent = request.agent( + wrapInApp(createOAuthRouteHandlers(baseConfig)), + ); + + agent.jar.setCookie( + 'my-provider-nonce=123', + '127.0.0.1', + '/my-provider/handler', + ); + + agent.jar.setCookie( + `my-provider-refresh-token-0=${extractTokenStringSlice( + fiveKilobyteRefreshToken, + 0, + )}; Domain=127.0.0.1`, + '127.0.0.1', + '/my-provider', + ); + agent.jar.setCookie( + `my-provider-refresh-token-1=${extractTokenStringSlice( + fiveKilobyteRefreshToken, + 1, + )}; Domain=127.0.0.1`, + '127.0.0.1', + '/my-provider', + ); + + mockAuthenticator.authenticate.mockResolvedValue({ + fullProfile: { id: 'id' } as PassportProfile, + session: mockSession, + }); + + const res = await agent.get('/my-provider/handler/frame').query({ + state: encodeOAuthState({ + env: 'development', + nonce: '123', + } as OAuthState), + }); + + expect(res.status).toBe(200); + + expect( + confirmRefreshTokenCookieDeletion(res, { + chunkNumber: 0, + domain: false, + }), + ).toBe(true); + expect( + confirmRefreshTokenCookieDeletion(res, { + chunkNumber: 1, + domain: false, + }), + ).toBe(true); + + expect(getRefreshTokenCookie(agent).value).toBe('refresh-token'); + expect(getRefreshTokenCookie(agent, 0)).toBeUndefined(); + expect(getRefreshTokenCookie(agent, 1)).toBeUndefined(); + }); + it('should authenticate with sign-in, profile transform, and persisted scopes', async () => { const agent = request.agent( wrapInApp( @@ -632,15 +956,18 @@ describe('createOAuthRouteHandlers', () => { ); agent.jar.setCookie( - `my-provider-refresh-token-0=${fiveKilobyteRefreshToken.slice( + `my-provider-refresh-token-0=${extractTokenStringSlice( + fiveKilobyteRefreshToken, 0, - 4000, )}`, '127.0.0.1', '/my-provider', ); agent.jar.setCookie( - `my-provider-refresh-token-1=${fiveKilobyteRefreshToken.slice(4000)}`, + `my-provider-refresh-token-1=${extractTokenStringSlice( + fiveKilobyteRefreshToken, + 1, + )}`, '127.0.0.1', '/my-provider', ); @@ -877,10 +1204,10 @@ describe('createOAuthRouteHandlers', () => { const expectedExpirationDate = Date.now() + 1000 * 24 * 60 * 60 * 1000; const cookie = getRefreshTokenCookie(agent); expect(cookie.expiration_date).toBeGreaterThanOrEqual( - expectedExpirationDate - 1000, + expectedExpirationDate - 5000, ); expect(cookie.expiration_date).toBeLessThanOrEqual( - expectedExpirationDate + 1000, + expectedExpirationDate + 5000, ); }); }); diff --git a/plugins/auth-react/CHANGELOG.md b/plugins/auth-react/CHANGELOG.md index 476ce92908..8d5ee31f64 100644 --- a/plugins/auth-react/CHANGELOG.md +++ b/plugins/auth-react/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-auth-react +## 0.1.22 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + +## 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 + ## 0.1.21 ### Patch Changes diff --git a/plugins/auth-react/package.json b/plugins/auth-react/package.json index 1383732d21..3a11292642 100644 --- a/plugins/auth-react/package.json +++ b/plugins/auth-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-react", - "version": "0.1.21", + "version": "0.1.22", "description": "Web library for the auth plugin", "backstage": { "role": "web-library", diff --git a/plugins/auth/CHANGELOG.md b/plugins/auth/CHANGELOG.md index ddecec85b7..f402cffbd3 100644 --- a/plugins/auth/CHANGELOG.md +++ b/plugins/auth/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-auth +## 0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/theme@0.7.1 + +## 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 + ## 0.1.2 ### Patch Changes diff --git a/plugins/auth/package.json b/plugins/auth/package.json index 389cd038ac..629d00efa9 100644 --- a/plugins/auth/package.json +++ b/plugins/auth/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth", - "version": "0.1.2", + "version": "0.1.3", "backstage": { "role": "frontend-plugin", "pluginId": "auth", diff --git a/plugins/bitbucket-cloud-common/CHANGELOG.md b/plugins/bitbucket-cloud-common/CHANGELOG.md index 1c0ef21684..2eb6fd31b6 100644 --- a/plugins/bitbucket-cloud-common/CHANGELOG.md +++ b/plugins/bitbucket-cloud-common/CHANGELOG.md @@ -1,5 +1,40 @@ # @backstage/plugin-bitbucket-cloud-common +## 0.3.5 + +### Patch Changes + +- 37fba1d: Added support for Bitbucket Cloud OAuth. This introduces an alternative authentication method using a workspace OAuth consumer, alongside App Passwords (deprecated) and API tokens. OAuth does not require a bot or service account and avoids token expiry issues. + + **BREAKING CHANGES** + + - **@backstage/integration** (`src/bitbucketCloud/core.ts`) + + - `getBitbucketCloudRequestOptions` now returns a `Promise` and **must** be awaited. + + - **@backstage/plugin-scaffolder-backend-module-bitbucket-cloud** (`src/actions/helpers.ts`) + - `getBitbucketClient` now returns a `Promise` and **must** be awaited. + - `getAuthorizationHeader` now returns a `Promise` and **must** be awaited. + + **OAuth usage example** + + ```yaml + integrations: + bitbucketCloud: + - clientId: client-id + clientSecret: client-secret + ``` + +- Updated dependencies + - @backstage/integration@1.19.0 + +## 0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.0 + ## 0.3.4 ### Patch Changes diff --git a/plugins/bitbucket-cloud-common/package.json b/plugins/bitbucket-cloud-common/package.json index cd9bc6cfe3..ac47687ae3 100644 --- a/plugins/bitbucket-cloud-common/package.json +++ b/plugins/bitbucket-cloud-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bitbucket-cloud-common", - "version": "0.3.4", + "version": "0.3.5", "description": "Common functionalities for bitbucket-cloud plugins", "backstage": { "role": "common-library", diff --git a/plugins/bitbucket-cloud-common/src/BitbucketCloudClient.ts b/plugins/bitbucket-cloud-common/src/BitbucketCloudClient.ts index adbf08cf40..eb40b51b5b 100644 --- a/plugins/bitbucket-cloud-common/src/BitbucketCloudClient.ts +++ b/plugins/bitbucket-cloud-common/src/BitbucketCloudClient.ts @@ -14,7 +14,10 @@ * limitations under the License. */ -import { BitbucketCloudIntegrationConfig } from '@backstage/integration'; +import { + BitbucketCloudIntegrationConfig, + getBitbucketCloudOAuthToken, +} from '@backstage/integration'; import fetch, { Request } from 'cross-fetch'; import { Models } from './models'; import { WithPagination } from './pagination'; @@ -139,22 +142,32 @@ export class BitbucketCloudClient { } private async request(req: Request): Promise { - return fetch(req, { headers: this.getAuthHeaders() }).then( - (response: Response) => { - if (!response.ok) { - throw new Error( - `Unexpected response for ${req.method} ${req.url}. Expected 200 but got ${response.status} - ${response.statusText}`, - ); - } + const headers = await this.getAuthHeaders(); + return fetch(req, { headers }).then((response: Response) => { + if (!response.ok) { + throw new Error( + `Unexpected response for ${req.method} ${req.url}. Expected 200 but got ${response.status} - ${response.statusText}`, + ); + } - return response; - }, - ); + return response; + }); } - private getAuthHeaders(): Record { + private async getAuthHeaders(): Promise> { const headers: Record = {}; + // OAuth authentication (clientId/clientSecret) + if (this.config.clientId && this.config.clientSecret) { + const token = await getBitbucketCloudOAuthToken( + this.config.clientId, + this.config.clientSecret, + ); + headers.Authorization = `Bearer ${token}`; + return headers; + } + + // Basic authentication (username/token or username/appPassword) if ( this.config.username && (this.config.token ?? this.config.appPassword) diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md index 46021f54e7..ac72ead2d6 100644 --- a/plugins/catalog-backend-module-aws/CHANGELOG.md +++ b/plugins/catalog-backend-module-aws/CHANGELOG.md @@ -1,5 +1,55 @@ # @backstage/plugin-catalog-backend-module-aws +## 0.4.18 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-defaults@0.14.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-kubernetes-common@0.9.9 + - @backstage/plugin-catalog-node@1.20.1 + +## 0.4.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.19 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + +## 0.4.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + +## 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 + ## 0.4.17 ### Patch Changes diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index 787df3329e..09362dd8c4 100644 --- a/plugins/catalog-backend-module-aws/package.json +++ b/plugins/catalog-backend-module-aws/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-aws", - "version": "0.4.17", + "version": "0.4.18", "description": "A Backstage catalog backend module that helps integrate towards AWS", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-azure/CHANGELOG.md b/plugins/catalog-backend-module-azure/CHANGELOG.md index 03aa3b8cb9..aa61bd3b04 100644 --- a/plugins/catalog-backend-module-azure/CHANGELOG.md +++ b/plugins/catalog-backend-module-azure/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-catalog-backend-module-azure +## 0.3.12 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## 0.3.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## 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 + ## 0.3.11 ### Patch Changes diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index 362d62401f..802d80077b 100644 --- a/plugins/catalog-backend-module-azure/package.json +++ b/plugins/catalog-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-azure", - "version": "0.3.11", + "version": "0.3.12", "description": "A Backstage catalog backend module that helps integrate towards Azure", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md b/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md index 3abde81f7e..d43e5c0d4f 100644 --- a/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-catalog-backend-module-backstage-openapi +## 0.5.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.4 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## 0.5.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.4-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## 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 + ## 0.5.8 ### Patch Changes diff --git a/plugins/catalog-backend-module-backstage-openapi/package.json b/plugins/catalog-backend-module-backstage-openapi/package.json index 4acde5bc83..dbf8455dc4 100644 --- a/plugins/catalog-backend-module-backstage-openapi/package.json +++ b/plugins/catalog-backend-module-backstage-openapi/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-backstage-openapi", - "version": "0.5.8", + "version": "0.5.9", "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", diff --git a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md index c60eb16b8a..52b988de1c 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,44 @@ # @backstage/plugin-catalog-backend-module-bitbucket-cloud +## 0.5.6 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.5 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## 0.5.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-bitbucket-cloud-common@0.3.5-next.0 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## 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 + ## 0.5.5 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json index 7c1b136b69..acc69c594e 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/package.json +++ b/plugins/catalog-backend-module-bitbucket-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-cloud", - "version": "0.5.5", + "version": "0.5.6", "description": "A Backstage catalog backend module that helps integrate towards Bitbucket Cloud", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md index 30969b2c13..8e3144e659 100644 --- a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,43 @@ # @backstage/plugin-catalog-backend-module-bitbucket-server +## 0.5.6 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## 0.5.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## 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 + ## 0.5.5 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-server/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json index 90b8105590..ea4601e9c0 100644 --- a/plugins/catalog-backend-module-bitbucket-server/package.json +++ b/plugins/catalog-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-server", - "version": "0.5.5", + "version": "0.5.6", "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", diff --git a/plugins/catalog-backend-module-gcp/CHANGELOG.md b/plugins/catalog-backend-module-gcp/CHANGELOG.md index fe31b91cde..5102f3266f 100644 --- a/plugins/catalog-backend-module-gcp/CHANGELOG.md +++ b/plugins/catalog-backend-module-gcp/CHANGELOG.md @@ -1,5 +1,43 @@ # @backstage/plugin-catalog-backend-module-gcp +## 0.3.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-kubernetes-common@0.9.9 + - @backstage/plugin-catalog-node@1.20.1 + +## 0.3.15-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + +## 0.3.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + +## 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 + ## 0.3.14 ### Patch Changes diff --git a/plugins/catalog-backend-module-gcp/package.json b/plugins/catalog-backend-module-gcp/package.json index 9f73511184..620c35c84f 100644 --- a/plugins/catalog-backend-module-gcp/package.json +++ b/plugins/catalog-backend-module-gcp/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gcp", - "version": "0.3.14", + "version": "0.3.15", "description": "A Backstage catalog backend module that helps integrate towards GCP", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-gcp/src/providers/__snapshots__/GkeEntityProvider.test.ts.snap b/plugins/catalog-backend-module-gcp/src/providers/__snapshots__/GkeEntityProvider.test.ts.snap index c903bcd192..18fd9b58ce 100644 --- a/plugins/catalog-backend-module-gcp/src/providers/__snapshots__/GkeEntityProvider.test.ts.snap +++ b/plugins/catalog-backend-module-gcp/src/providers/__snapshots__/GkeEntityProvider.test.ts.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`GkeEntityProvider should return clusters as Resources 1`] = ` [MockFunction] { diff --git a/plugins/catalog-backend-module-gerrit/CHANGELOG.md b/plugins/catalog-backend-module-gerrit/CHANGELOG.md index 93c2ac9e6d..ff8c27cbab 100644 --- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md +++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-catalog-backend-module-gerrit +## 0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## 0.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## 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 + ## 0.3.8 ### Patch Changes diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index e85ca3dc13..4a336802e8 100644 --- a/plugins/catalog-backend-module-gerrit/package.json +++ b/plugins/catalog-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gerrit", - "version": "0.3.8", + "version": "0.3.9", "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", diff --git a/plugins/catalog-backend-module-gitea/CHANGELOG.md b/plugins/catalog-backend-module-gitea/CHANGELOG.md index a729a2bb85..fa4ed4f98e 100644 --- a/plugins/catalog-backend-module-gitea/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitea/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-catalog-backend-module-gitea +## 0.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## 0.1.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## 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 + ## 0.1.6 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitea/package.json b/plugins/catalog-backend-module-gitea/package.json index 5b47eaddb6..1077d3d050 100644 --- a/plugins/catalog-backend-module-gitea/package.json +++ b/plugins/catalog-backend-module-gitea/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gitea", - "version": "0.1.6", + "version": "0.1.7", "license": "Apache-2.0", "description": "The gitea backend module for the catalog plugin.", "main": "src/index.ts", diff --git a/plugins/catalog-backend-module-github-org/CHANGELOG.md b/plugins/catalog-backend-module-github-org/CHANGELOG.md index 9b7b85bcc9..5d084a5159 100644 --- a/plugins/catalog-backend-module-github-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-github-org/CHANGELOG.md @@ -1,5 +1,54 @@ # @backstage/plugin-catalog-backend-module-github-org +## 0.3.17 + +### Patch Changes + +- ed5a7a3: Introduce new configuration option to exclude suspended users from GitHub Enterprise instances. + + When it’s set to true, suspended users won’t be returned when querying the organization users for GitHub Enterprise instances. + Note that this option should be used only against GitHub Enterprise instances, the property does not exist in the github.com GraphQL schema, setting it will cause a schema validation error and the syncing of users will fail. + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/plugin-catalog-backend-module-github@0.12.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## 0.3.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-backend-module-github@0.11.3-next.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## 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 + +## 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 + ## 0.3.16 ### Patch Changes diff --git a/plugins/catalog-backend-module-github-org/package.json b/plugins/catalog-backend-module-github-org/package.json index fb6aa27a7b..d37a8c7a6e 100644 --- a/plugins/catalog-backend-module-github-org/package.json +++ b/plugins/catalog-backend-module-github-org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-github-org", - "version": "0.3.16", + "version": "0.3.17", "description": "The github-org backend module for the catalog plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-github-org/src/module.ts b/plugins/catalog-backend-module-github-org/src/module.ts index 2d349cb873..26abede133 100644 --- a/plugins/catalog-backend-module-github-org/src/module.ts +++ b/plugins/catalog-backend-module-github-org/src/module.ts @@ -121,6 +121,7 @@ export const catalogModuleGithubOrgEntityProvider = createBackendModule({ alwaysUseDefaultNamespace: definitions.length === 1 && definition.orgs?.length === 1, pageSizes: definition.pageSizes, + excludeSuspendedUsers: definition.excludeSuspendedUsers, }), ); } @@ -139,6 +140,7 @@ function readDefinitionsFromConfig(rootConfig: Config): Array<{ teamMembers?: number; organizationMembers?: number; }; + excludeSuspendedUsers?: boolean; }> { const baseKey = 'catalog.providers.githubOrg'; const baseConfig = rootConfig.getOptional(baseKey); @@ -166,5 +168,7 @@ function readDefinitionsFromConfig(rootConfig: Config): Array<{ ), } : undefined, + excludeSuspendedUsers: + c.getOptionalBoolean('excludeSuspendedUsers') ?? false, })); } diff --git a/plugins/catalog-backend-module-github/CHANGELOG.md b/plugins/catalog-backend-module-github/CHANGELOG.md index 00f185e694..4be66e6d20 100644 --- a/plugins/catalog-backend-module-github/CHANGELOG.md +++ b/plugins/catalog-backend-module-github/CHANGELOG.md @@ -1,5 +1,60 @@ # @backstage/plugin-catalog-backend-module-github +## 0.12.0 + +### Minor Changes + +- b3286d5: Added the `github.com/user-id` annotation to store GitHub's user ID (immutable) in user entities. Also includes addition of the `userIdMatchingUserEntityAnnotation` sign-in resolver that matches users by the new ID. + +### Patch Changes + +- ed5a7a3: Introduce new configuration option to exclude suspended users from GitHub Enterprise instances. + + When it’s set to true, suspended users won’t be returned when querying the organization users for GitHub Enterprise instances. + Note that this option should be used only against GitHub Enterprise instances, the property does not exist in the github.com GraphQL schema, setting it will cause a schema validation error and the syncing of users will fail. + +- a413977: Added configurable `pageSizes` option to `GithubOrgEntityProvider` for GitHub GraphQL API queries to prevent `RESOURCE_LIMITS_EXCEEDED` errors with organizations with large number of teams and members. This aligns the configuration options with `GithubMultiOrgEntityProvider`. +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## 0.11.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## 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. + +## 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 + ## 0.11.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-github/config.d.ts b/plugins/catalog-backend-module-github/config.d.ts index f3cbb15e06..5425cae9d0 100644 --- a/plugins/catalog-backend-module-github/config.d.ts +++ b/plugins/catalog-backend-module-github/config.d.ts @@ -264,6 +264,12 @@ export interface Config { */ orgs?: string[]; + /** + * (Optional) Only for GitHub Enterprise. Whether to exclude suspended users when querying organization users. + * Default: `false`. + */ + excludeSuspendedUsers?: boolean; + /** * The refresh schedule to use. */ @@ -315,6 +321,12 @@ export interface Config { */ orgs?: string[]; + /** + * (Optional) Only for GitHub Enterprise. Whether to exclude suspended users when querying organization users. + * Default: `false`. + */ + excludeSuspendedUsers?: boolean; + /** * The refresh schedule to use. */ diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index a9798decda..b37546ca00 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-github", - "version": "0.11.2", + "version": "0.12.0", "description": "A Backstage catalog backend module that helps integrate towards GitHub", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-github/report.api.md b/plugins/catalog-backend-module-github/report.api.md index 016bb6da65..25fc58be79 100644 --- a/plugins/catalog-backend-module-github/report.api.md +++ b/plugins/catalog-backend-module-github/report.api.md @@ -151,6 +151,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { teamTransformer?: TeamTransformer; alwaysUseDefaultNamespace?: boolean; pageSizes?: Partial; + excludeSuspendedUsers?: boolean; }); connect(connection: EntityProviderConnection): Promise; // (undocumented) @@ -166,6 +167,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { export interface GithubMultiOrgEntityProviderOptions { alwaysUseDefaultNamespace?: boolean; events?: EventsService; + excludeSuspendedUsers?: boolean; githubCredentialsProvider?: GithubCredentialsProvider; githubUrl: string; id: string; @@ -227,6 +229,8 @@ export class GithubOrgEntityProvider implements EntityProvider { githubCredentialsProvider?: GithubCredentialsProvider; userTransformer?: UserTransformer; teamTransformer?: TeamTransformer; + pageSizes?: Partial; + excludeSuspendedUsers?: boolean; }); connect(connection: EntityProviderConnection): Promise; // (undocumented) @@ -244,10 +248,12 @@ export type GitHubOrgEntityProviderOptions = GithubOrgEntityProviderOptions; // @public export interface GithubOrgEntityProviderOptions { events?: EventsService; + excludeSuspendedUsers?: boolean; githubCredentialsProvider?: GithubCredentialsProvider; id: string; logger: LoggerService; orgUrl: string; + pageSizes?: Partial; schedule?: 'manual' | SchedulerServiceTaskRunner; teamTransformer?: TeamTransformer; userTransformer?: UserTransformer; @@ -301,11 +307,13 @@ export type GithubTeam = { // @public export type GithubUser = { login: string; + id?: string; bio?: string; avatarUrl?: string; email?: string; name?: string; organizationVerifiedDomainEmails?: string[]; + suspendedAt?: string; }; // @public diff --git a/plugins/catalog-backend-module-github/src/lib/annotation.ts b/plugins/catalog-backend-module-github/src/lib/annotation.ts index 4db6ffb975..2d06388d65 100644 --- a/plugins/catalog-backend-module-github/src/lib/annotation.ts +++ b/plugins/catalog-backend-module-github/src/lib/annotation.ts @@ -35,3 +35,13 @@ export const ANNOTATION_GITHUB_USER_LOGIN = 'github.com/user-login'; * @public */ export const ANNOTATION_GITHUB_TEAM_SLUG = 'github.com/team-slug'; + +/** + * The value of this annotation is the unique identifier for a user on + * [GitHub](https://github.com) (either the public one, or a private GitHub + * Enterprise installation) that is related to this entity. This is a stable + * identifier that persists even if the user changes their login name. + * + * @public + */ +export const ANNOTATION_GITHUB_USER_ID = 'github.com/user-id'; diff --git a/plugins/catalog-backend-module-github/src/lib/defaultTransformers.ts b/plugins/catalog-backend-module-github/src/lib/defaultTransformers.ts index 6befd32056..4de9af89c8 100644 --- a/plugins/catalog-backend-module-github/src/lib/defaultTransformers.ts +++ b/plugins/catalog-backend-module-github/src/lib/defaultTransformers.ts @@ -19,6 +19,7 @@ import { graphql } from '@octokit/graphql'; import { ANNOTATION_GITHUB_TEAM_SLUG, ANNOTATION_GITHUB_USER_LOGIN, + ANNOTATION_GITHUB_USER_ID, } from './annotation'; import { GithubTeam, GithubUser } from './github'; @@ -69,6 +70,7 @@ export const defaultUserTransformer = async ( name: item.login, annotations: { [ANNOTATION_GITHUB_USER_LOGIN]: item.login, + ...(item.id && { [ANNOTATION_GITHUB_USER_ID]: item.id }), }, }, spec: { diff --git a/plugins/catalog-backend-module-github/src/lib/github.test.ts b/plugins/catalog-backend-module-github/src/lib/github.test.ts index ca9b3eb692..6933c9a245 100644 --- a/plugins/catalog-backend-module-github/src/lib/github.test.ts +++ b/plugins/catalog-backend-module-github/src/lib/github.test.ts @@ -166,6 +166,54 @@ describe('github', () => { getOrganizationUsers(graphql, 'a', 'token'), ).resolves.toEqual(output); }); + + it('reads members excluding suspended users', async () => { + const input: QueryResponse = { + organization: { + membersWithRole: { + pageInfo: { hasNextPage: false }, + nodes: [ + { + login: 'a', + name: 'b', + bio: 'c', + email: 'd', + avatarUrl: 'e', + suspendedAt: '2025-01-01', + }, + { + login: 'a', + name: 'b', + bio: 'c', + email: 'd', + avatarUrl: 'e', + suspendedAt: undefined, + }, + ], + }, + }, + }; + + const output = { + users: [ + expect.objectContaining({ + metadata: expect.objectContaining({ name: 'a', description: 'c' }), + spec: { + profile: { displayName: 'b', email: 'd', picture: 'e' }, + memberOf: [], + }, + }), + ], + }; + + server.use( + graphqlMsw.query('users', () => HttpResponse.json({ data: input })), + ); + + await expect( + getOrganizationUsers(graphql, 'a', 'token', undefined, undefined, true), + ).resolves.toEqual(output); + }); }); describe('getOrganizationUsers using custom UserTransformer', () => { @@ -279,6 +327,63 @@ describe('github', () => { expect(users.users).toHaveLength(1); expect(users).toEqual(output); }); + + it('reads members including suspended users', async () => { + const input: QueryResponse = { + organization: { + membersWithRole: { + pageInfo: { hasNextPage: false }, + nodes: [ + { + login: 'a', + name: 'b', + bio: 'c', + email: 'd', + avatarUrl: 'e', + }, + { + login: 'ab', + name: 'bb', + bio: 'cc', + email: 'dd', + avatarUrl: 'ee', + suspendedAt: '2025-01-01', + }, + ], + }, + }, + }; + + const output = { + users: [ + expect.objectContaining({ + metadata: expect.objectContaining({ + name: 'a-custom', + }), + }), + expect.objectContaining({ + metadata: expect.objectContaining({ + name: 'ab-custom', + }), + }), + ], + }; + + server.use( + graphqlMsw.query('users', () => HttpResponse.json({ data: input })), + ); + + await expect( + getOrganizationUsers( + graphql, + 'a', + 'token', + customUserTransformer, + undefined, + false, + ), + ).resolves.toEqual(output); + }); }); describe('getOrganizationTeams using default TeamTransformer', () => { diff --git a/plugins/catalog-backend-module-github/src/lib/github.ts b/plugins/catalog-backend-module-github/src/lib/github.ts index 625fb57bba..0690ca125b 100644 --- a/plugins/catalog-backend-module-github/src/lib/github.ts +++ b/plugins/catalog-backend-module-github/src/lib/github.ts @@ -112,11 +112,13 @@ export type GithubOrg = { */ export type GithubUser = { login: string; + id?: string; bio?: string; avatarUrl?: string; email?: string; name?: string; organizationVerifiedDomainEmails?: string[]; + suspendedAt?: string; }; /** @@ -181,6 +183,7 @@ export type Connection = { * @param tokenType - The type of GitHub credential * @param userTransformer - Optional transformer for user entities * @param pageSizes - Optional page sizes configuration + * @param excludeSuspendedUsers - Optional flag to exclude suspended users (only for GitHub Enterprise instances) */ export async function getOrganizationUsers( client: typeof graphql, @@ -188,7 +191,9 @@ export async function getOrganizationUsers( tokenType: GithubCredentialType, userTransformer: UserTransformer = defaultUserTransformer, pageSizes: GithubPageSizes = DEFAULT_PAGE_SIZES, + excludeSuspendedUsers: boolean = false, ): Promise<{ users: Entity[] }> { + const suspendedAtField = excludeSuspendedUsers ? 'suspendedAt,' : ''; const query = ` query users($org: String!, $email: Boolean!, $cursor: String, $organizationMembersPageSize: Int!) { organization(login: $org) { @@ -198,8 +203,10 @@ export async function getOrganizationUsers( avatarUrl, bio, email @include(if: $email), + id, login, name, + ${suspendedAtField} organizationVerifiedDomainEmails(login: $org) } } @@ -209,18 +216,19 @@ export async function getOrganizationUsers( // There is no user -> teams edge, so we leave the memberships empty for // now and let the team iteration handle it instead - const users = await queryWithPaging( + const users = await queryWithPaging({ client, query, org, - r => r.organization?.membersWithRole, - userTransformer, - { + connection: r => r.organization?.membersWithRole, + transformer: userTransformer, + variables: { org, email: tokenType === 'token', organizationMembersPageSize: pageSizes.organizationMembers, }, - ); + filter: u => (excludeSuspendedUsers ? !u.suspendedAt : true), + }); return { users }; } @@ -262,6 +270,7 @@ export async function getOrganizationTeams( avatarUrl, bio, email, + id, login, name, organizationVerifiedDomainEmails(login: $org) @@ -305,18 +314,18 @@ export async function getOrganizationTeams( return await teamTransformer(team, ctx); }; - const teams = await queryWithPaging( + const teams = await queryWithPaging({ client, query, org, - r => r.organization?.teams, - materialisedTeams, - { + connection: r => r.organization?.teams, + transformer: materialisedTeams, + variables: { org, teamsPageSize: pageSizes.teams, membersPageSize: pageSizes.teamMembers, }, - ); + }); return { teams }; } @@ -356,6 +365,7 @@ export async function getOrganizationTeamsFromUsers( avatarUrl, bio, email, + id, login, name, organizationVerifiedDomainEmails(login: $org) @@ -399,19 +409,19 @@ export async function getOrganizationTeamsFromUsers( return await teamTransformer(team, ctx); }; - const teams = await queryWithPaging( + const teams = await queryWithPaging({ client, query, org, - r => r.organization?.teams, - materialisedTeams, - { + connection: r => r.organization?.teams, + transformer: materialisedTeams, + variables: { org, userLogins, teamsPageSize: pageSizes.teams, membersPageSize: pageSizes.teamMembers, }, - ); + }); return { teams }; } @@ -458,14 +468,14 @@ export async function getOrganizationTeamsForUser( return await teamTransformer(team, ctx); }; - const teams = await queryWithPaging( + const teams = await queryWithPaging({ client, query, org, - r => r.organization?.teams, - materialisedTeams, - { org, userLogins: [userLogin], teamsPageSize: pageSizes.teams }, - ); + connection: r => r.organization?.teams, + transformer: materialisedTeams, + variables: { org, userLogins: [userLogin], teamsPageSize: pageSizes.teams }, + }); return { teams }; } @@ -486,14 +496,14 @@ export async function getOrganizationsFromUser( } }`; - const orgs = await queryWithPaging( + const orgs = await queryWithPaging({ client, query, - '', - r => r.user?.organizations, - async o => o.login, - { user }, - ); + org: '', + connection: r => r.user?.organizations, + transformer: async o => o.login, + variables: { user }, + }); return { orgs }; } @@ -632,14 +642,18 @@ export async function getOrganizationRepositories( } }`; - const repositories = await queryWithPaging( + const repositories = await queryWithPaging({ client, query, org, - r => r.repositoryOwner?.repositories, - async x => x, - { org, catalogPathRef, repositoriesPageSize: pageSizes.repositories }, - ); + connection: r => r.repositoryOwner?.repositories, + transformer: async x => x, + variables: { + org, + catalogPathRef, + repositoriesPageSize: pageSizes.repositories, + }, + }); return { repositories }; } @@ -727,14 +741,14 @@ export async function getTeamMembers( } }`; - const members = await queryWithPaging( + const members = await queryWithPaging({ client, query, org, - r => r.organization?.team?.members, - async user => user, - { org, teamSlug, membersPageSize: pageSizes.teamMembers }, - ); + connection: r => r.organization?.team?.members, + transformer: async user => user, + variables: { org, teamSlug, membersPageSize: pageSizes.teamMembers }, + }); return { members }; } @@ -748,31 +762,36 @@ export async function getTeamMembers( * * Requires that the query accepts a $cursor variable. * - * @param client - The octokit client - * @param query - The query to execute - * @param org - The slug of the org to read - * @param connection - A function that, given the response, picks out the actual + * @param params - Object containing all parameters + * @param params.client - The octokit client + * @param params.query - The query to execute + * @param params.org - The slug of the org to read + * @param params.connection - A function that, given the response, picks out the actual * Connection object that's being iterated - * @param transformer - A function that, given one of the nodes in the Connection, + * @param params.transformer - A function that, given one of the nodes in the Connection, * returns the model mapped form of it - * @param variables - The variable values that the query needs, minus the cursor + * @param params.variables - The variable values that the query needs, minus the cursor + * @param params.filter - An optional filter function to filter the nodes before transforming them */ export async function queryWithPaging< GraphqlType, OutputType, Variables extends {}, Response = QueryResponse, ->( - client: typeof graphql, - query: string, - org: string, - connection: (response: Response) => Connection | undefined, +>(params: { + client: typeof graphql; + query: string; + org: string; + connection: (response: Response) => Connection | undefined; transformer: ( item: GraphqlType, ctx: TransformerContext, - ) => Promise, - variables: Variables, -): Promise { + ) => Promise; + variables: Variables; + filter?: (item: GraphqlType) => boolean; +}): Promise { + const { client, query, org, connection, transformer, variables, filter } = + params; const result: OutputType[] = []; const sleep = (ms: number) => new Promise(r => setTimeout(r, ms)); @@ -789,6 +808,9 @@ export async function queryWithPaging< } for (const node of conn.nodes) { + if (filter && !filter(node)) { + continue; + } const transformedNode = await transformer(node, { client, query, diff --git a/plugins/catalog-backend-module-github/src/lib/index.ts b/plugins/catalog-backend-module-github/src/lib/index.ts index ac73430d33..48050e96bb 100644 --- a/plugins/catalog-backend-module-github/src/lib/index.ts +++ b/plugins/catalog-backend-module-github/src/lib/index.ts @@ -38,3 +38,8 @@ export { buildOrgHierarchy, } from './org'; export { parseGithubOrgUrl } from './util'; +export { + ANNOTATION_GITHUB_USER_LOGIN, + ANNOTATION_GITHUB_TEAM_SLUG, + ANNOTATION_GITHUB_USER_ID, +} from './annotation'; diff --git a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts index 1aa1af46d7..e80579ee36 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts @@ -94,6 +94,7 @@ describe('GithubMultiOrgEntityProvider', () => { nodes: [ { login: 'a', + id: 'f', name: 'b', bio: 'c', email: 'd', @@ -101,6 +102,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, { login: 'x', + id: 'g', name: 'y', bio: 'z', email: 'w', @@ -128,7 +130,10 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a' }, { login: 'x' }], + nodes: [ + { login: 'a', id: 'f' }, + { login: 'x', id: 'g' }, + ], }, }, ], @@ -156,6 +161,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, { login: 'q', + id: 'h', name: 'r', bio: 's', email: 't', @@ -183,7 +189,10 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a' }, { login: 'q' }], + nodes: [ + { login: 'a', id: 'f' }, + { login: 'q', id: 'h' }, + ], }, }, ], @@ -215,6 +224,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/a', 'github.com/user-login': 'a', + 'github.com/user-id': 'f', }, description: 'c', name: 'a', @@ -241,6 +251,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/x', 'github.com/user-login': 'x', + 'github.com/user-id': 'g', }, description: 'z', name: 'x', @@ -267,6 +278,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/q', 'github.com/user-login': 'q', + 'github.com/user-id': 'h', }, description: 's', name: 'q', @@ -382,6 +394,7 @@ describe('GithubMultiOrgEntityProvider', () => { nodes: [ { login: 'a', + id: 'f', name: 'b', bio: 'c', email: 'd', @@ -389,6 +402,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, { login: 'x', + id: 'g', name: 'y', bio: 'z', email: 'w', @@ -416,7 +430,10 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a' }, { login: 'x' }], + nodes: [ + { login: 'a', id: 'f' }, + { login: 'x', id: 'g' }, + ], }, }, ], @@ -444,6 +461,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, { login: 'q', + id: 'h', name: 'r', bio: 's', email: 't', @@ -471,7 +489,10 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a' }, { login: 'q' }], + nodes: [ + { login: 'a', id: 'f' }, + { login: 'q', id: 'h' }, + ], }, }, ], @@ -517,6 +538,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/a', 'github.com/user-login': 'a', + 'github.com/user-id': 'f', }, description: 'c', name: 'a', @@ -543,6 +565,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/x', 'github.com/user-login': 'x', + 'github.com/user-id': 'g', }, description: 'z', name: 'x', @@ -569,6 +592,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/q', 'github.com/user-login': 'q', + 'github.com/user-id': 'h', }, description: 's', name: 'q', @@ -656,6 +680,7 @@ describe('GithubMultiOrgEntityProvider', () => { nodes: [ { login: 'a', + id: 'f', name: 'b', bio: 'c', email: 'd', @@ -663,6 +688,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, { login: 'x', + id: 'g', name: 'y', bio: 'z', email: 'w', @@ -690,7 +716,10 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a' }, { login: 'x' }], + nodes: [ + { login: 'a', id: 'f' }, + { login: 'x', id: 'g' }, + ], }, }, ], @@ -718,6 +747,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, { login: 'q', + id: 'h', name: 'r', bio: 's', email: 't', @@ -745,7 +775,10 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a' }, { login: 'q' }], + nodes: [ + { login: 'a', id: 'f' }, + { login: 'q', id: 'h' }, + ], }, }, ], @@ -783,6 +816,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/a', 'github.com/user-login': 'a', + 'github.com/user-id': 'f', }, description: 'c', name: 'a', @@ -809,6 +843,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/x', 'github.com/user-login': 'x', + 'github.com/user-id': 'g', }, description: 'z', name: 'x', @@ -835,6 +870,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/q', 'github.com/user-login': 'q', + 'github.com/user-id': 'h', }, description: 's', name: 'q', @@ -1099,6 +1135,7 @@ describe('GithubMultiOrgEntityProvider', () => { nodes: [ { login: 'a', + id: 'f', name: 'b', bio: 'c', email: 'd', @@ -1126,7 +1163,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a' }], + nodes: [{ login: 'a', id: 'f' }], }, }, ], @@ -1151,7 +1188,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a' }], + nodes: [{ login: 'a', id: 'f' }], }, }, ], @@ -1176,7 +1213,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a' }], + nodes: [{ login: 'a', id: 'f' }], }, }, ], @@ -1213,6 +1250,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/a', 'github.com/user-login': 'a', + 'github.com/user-id': 'f', }, description: 'c', name: 'a', @@ -1300,7 +1338,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a', name: 'a' }], + nodes: [{ login: 'a', id: 'f', name: 'a' }], }, }, ], @@ -1328,6 +1366,7 @@ describe('GithubMultiOrgEntityProvider', () => { membership: { user: { name: 'a', + node_id: 'f', avatar_url: 'https://avatars.githubusercontent.com/u/83820368', email: 'user1@test.com', login: 'a', @@ -1351,6 +1390,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/a', 'github.com/user-login': 'a', + 'github.com/user-id': 'f', }, name: 'a', }, @@ -1398,6 +1438,7 @@ describe('GithubMultiOrgEntityProvider', () => { membership: { user: { name: 'a', + node_id: 'f', avatar_url: 'https://avatars.githubusercontent.com/u/83820368', email: 'user1@test.com', login: 'a', @@ -1422,6 +1463,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/a', 'github.com/user-login': 'a', + 'github.com/user-id': 'f', }, name: 'a', }, @@ -1473,7 +1515,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a', name: 'a' }], + nodes: [{ login: 'a', id: 'f', name: 'a' }], }, }, ], @@ -1493,6 +1535,7 @@ describe('GithubMultiOrgEntityProvider', () => { membership: { user: { name: 'a', + node_id: 'f', avatar_url: 'https://avatars.githubusercontent.com/u/83820368', email: 'user1@test.com', login: 'a', @@ -1516,6 +1559,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/a', 'github.com/user-login': 'a', + 'github.com/user-id': 'f', }, name: 'a', }, @@ -1729,7 +1773,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a', name: 'a' }], + nodes: [{ login: 'a', id: 'f', name: 'a' }], }, }, }, @@ -1741,6 +1785,7 @@ describe('GithubMultiOrgEntityProvider', () => { nodes: [ { login: 'a', + id: 'f', name: 'b', bio: 'c', email: 'd', @@ -1748,6 +1793,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, { login: 'w', + id: 'i', name: 'x', bio: 'y', email: 'z', @@ -1774,7 +1820,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a', name: 'a' }], + nodes: [{ login: 'a', id: 'f', name: 'a' }], }, }, ], @@ -1798,7 +1844,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a', name: 'a' }], + nodes: [{ login: 'a', id: 'f', name: 'a' }], }, }, ], @@ -1847,6 +1893,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/a', 'github.com/user-login': 'a', + 'github.com/user-id': 'f', }, name: 'a', description: 'c', @@ -2110,7 +2157,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a', name: 'a' }], + nodes: [{ login: 'a', id: 'f', name: 'a' }], }, }, }, @@ -2147,7 +2194,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a', name: 'a' }], + nodes: [{ login: 'a', id: 'f', name: 'a' }], }, }, ], @@ -2171,7 +2218,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a', name: 'a' }], + nodes: [{ login: 'a', id: 'f', name: 'a' }], }, }, ], @@ -2194,6 +2241,7 @@ describe('GithubMultiOrgEntityProvider', () => { member: { name: 'a', login: 'a', + node_id: 'f', avatar_url: 'https://avatars.githubusercontent.com/u/83820368', email: 'user1@test.com', }, @@ -2245,6 +2293,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/a', 'github.com/user-login': 'a', + 'github.com/user-id': 'f', }, name: 'a', }, @@ -2327,7 +2376,7 @@ describe('GithubMultiOrgEntityProvider', () => { }, members: { pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a', name: 'a' }], + nodes: [{ login: 'a', id: 'f', name: 'a' }], }, }, ], @@ -2350,6 +2399,7 @@ describe('GithubMultiOrgEntityProvider', () => { member: { name: 'a', login: 'a', + node_id: 'f', avatar_url: 'https://avatars.githubusercontent.com/u/83820368', email: 'user1@test.com', }, @@ -2401,6 +2451,7 @@ describe('GithubMultiOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/a', 'github.com/user-login': 'a', + 'github.com/user-id': 'f', }, name: 'a', }, diff --git a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts index 839ca66e40..28d598b5b1 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts @@ -174,6 +174,14 @@ export interface GithubMultiOrgEntityProviderOptions { * Reduce these values if hitting RESOURCE_LIMITS_EXCEEDED errors. */ pageSizes?: Partial; + + /** + * Optionally exclude suspended users when querying organization users. + * @defaultValue false + * @remarks + * Only for GitHub Enterprise instances. Will error if used against GitHub.com API. + */ + excludeSuspendedUsers?: boolean; } type CreateDeltaOperation = (entities: Entity[]) => { @@ -221,6 +229,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { events: options.events, alwaysUseDefaultNamespace: options.alwaysUseDefaultNamespace, pageSizes: options.pageSizes, + excludeSuspendedUsers: options.excludeSuspendedUsers, }); provider.schedule(options.schedule); @@ -241,6 +250,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { teamTransformer?: TeamTransformer; alwaysUseDefaultNamespace?: boolean; pageSizes?: Partial; + excludeSuspendedUsers?: boolean; }, ) {} @@ -306,6 +316,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { tokenType, this.options.userTransformer, pageSizes, + this.options.excludeSuspendedUsers, ); const { teams } = await getOrganizationTeams( @@ -458,6 +469,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { tokenType, this.options.userTransformer, pageSizes, + this.options.excludeSuspendedUsers, ); const { teams } = await getOrganizationTeams( @@ -519,7 +531,13 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { const userTransformer = this.options.userTransformer || defaultUserTransformer; - const { name, avatar_url: avatarUrl, email, login } = event.membership.user; + const { + name, + avatar_url: avatarUrl, + email, + login, + node_id, + } = event.membership.user; const org = event.organization.login; const { headers } = await this.options.githubCredentialsProvider.getCredentials({ @@ -561,6 +579,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { avatarUrl, login, email: email ?? undefined, + id: node_id, }, { org, @@ -692,6 +711,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { tokenType, this.options.userTransformer, pageSizes, + this.options.excludeSuspendedUsers, ); const usersFromChangedGroup = isGroupEntity(team) @@ -805,13 +825,14 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { const userTransformer = this.options.userTransformer || defaultUserTransformer; - const { name, avatar_url: avatarUrl, email, login } = event.member; + const { name, avatar_url: avatarUrl, email, login, node_id } = event.member; const user = await userTransformer( { name, avatarUrl, login, email: email ?? undefined, + id: node_id, }, { org, diff --git a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts index 17fa00b137..9be5a3e161 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts @@ -89,6 +89,7 @@ describe('GithubOrgEntityProvider', () => { nodes: [ { login: 'a', + id: 'f', name: 'b', bio: 'c', email: 'd', @@ -136,6 +137,7 @@ describe('GithubOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://https://github.com/a', 'github.com/user-login': 'a', + 'github.com/user-id': 'f', }, description: 'c', name: 'a', @@ -311,6 +313,7 @@ describe('GithubOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/githubuser', 'github.com/user-login': 'githubuser', + 'github.com/user-id': 'githubuserId', }, }, spec: { @@ -331,6 +334,7 @@ describe('GithubOrgEntityProvider', () => { user: { name: 'githubuser', login: 'githubuser', + node_id: 'githubuserId', avatar_url: 'https://avatars.githubusercontent.com/u/83820368', email: 'user1@test.com', }, @@ -398,6 +402,7 @@ describe('GithubOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/githubuser', 'github.com/user-login': 'githubuser', + 'github.com/user-id': 'githubuserId', }, }, spec: { @@ -418,6 +423,7 @@ describe('GithubOrgEntityProvider', () => { user: { name: 'githubuser', login: 'githubuser', + node_id: 'githubuserId', avatar_url: 'https://avatars.githubusercontent.com/u/83820368', email: 'user1@test.com', }, @@ -778,6 +784,7 @@ describe('GithubOrgEntityProvider', () => { nodes: [ { login: 'a', + id: 'f', name: 'b', bio: 'c', email: 'd', @@ -785,6 +792,7 @@ describe('GithubOrgEntityProvider', () => { }, { login: 'githubuser', + id: 'githubuserId', name: 'githubuser', bio: 'githubuser', email: 'd', @@ -865,6 +873,7 @@ describe('GithubOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/a', 'github.com/user-login': 'a', + 'github.com/user-id': 'f', }, name: 'a', description: 'c', @@ -891,6 +900,7 @@ describe('GithubOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/githubuser', 'github.com/user-login': 'githubuser', + 'github.com/user-id': 'githubuserId', }, name: 'githubuser', description: 'githubuser', @@ -1029,6 +1039,7 @@ describe('GithubOrgEntityProvider', () => { nodes: [ { login: 'a', + id: 'f', name: 'b', bio: 'c', email: 'd', @@ -1036,6 +1047,7 @@ describe('GithubOrgEntityProvider', () => { }, { login: 'githubuser', + id: 'githubuserId', name: 'githubuser', bio: 'githubuser', email: 'd', @@ -1112,6 +1124,7 @@ describe('GithubOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/githubuser', 'github.com/user-login': 'githubuser', + 'github.com/user-id': 'githubuserId', }, name: 'githubuser', description: 'githubuser', @@ -1168,6 +1181,7 @@ describe('GithubOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/githubuser', 'github.com/user-login': 'githubuser', + 'github.com/user-id': 'githubuserId', }, name: 'githubuser', description: 'githubuser', @@ -1277,6 +1291,7 @@ describe('GithubOrgEntityProvider', () => { nodes: [ { login: 'githubuser', + id: 'githubuserId', name: 'githubuser', bio: 'githubuser', email: 'd', @@ -1336,6 +1351,7 @@ describe('GithubOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/githubuser', 'github.com/user-login': 'githubuser', + 'github.com/user-id': 'githubuserId', }, name: 'githubuser', description: 'githubuser', @@ -1392,6 +1408,7 @@ describe('GithubOrgEntityProvider', () => { 'backstage.io/managed-by-origin-location': 'url:https://github.com/githubuser', 'github.com/user-login': 'githubuser', + 'github.com/user-id': 'githubuserId', }, name: 'githubuser', description: 'githubuser', diff --git a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts index 6dfa8a07f8..bb4b268e62 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts @@ -53,11 +53,13 @@ import { createGraphqlClient, createRemoveEntitiesOperation, createReplaceEntitiesOperation, + DEFAULT_PAGE_SIZES, DeferredEntitiesBuilder, getOrganizationTeam, getOrganizationTeams, getOrganizationTeamsFromUsers, getOrganizationUsers, + GithubPageSizes, GithubTeam, } from '../lib/github'; import { areGroupEntities, areUserEntities } from '../lib/guards'; @@ -130,6 +132,20 @@ export interface GithubOrgEntityProviderOptions { * Optionally include a team transformer for transforming from GitHub teams to Group Entities */ teamTransformer?: TeamTransformer; + + /** + * Optionally configure page sizes for GitHub GraphQL API queries. + * Reduce these values if hitting RESOURCE_LIMITS_EXCEEDED errors. + */ + pageSizes?: Partial; + + /** + * Optionally exclude suspended users when querying organization users. + * @defaultValue false + * @remarks + * Only for GitHub Enterprise instances. Will error if used against GitHub.com API. + */ + excludeSuspendedUsers?: boolean; } /** @@ -167,6 +183,8 @@ export class GithubOrgEntityProvider implements EntityProvider { userTransformer: options.userTransformer, teamTransformer: options.teamTransformer, events: options.events, + pageSizes: options.pageSizes, + excludeSuspendedUsers: options.excludeSuspendedUsers, }); provider.schedule(options.schedule); @@ -184,6 +202,8 @@ export class GithubOrgEntityProvider implements EntityProvider { githubCredentialsProvider?: GithubCredentialsProvider; userTransformer?: UserTransformer; teamTransformer?: TeamTransformer; + pageSizes?: Partial; + excludeSuspendedUsers?: boolean; }, ) { this.credentialsProvider = @@ -196,6 +216,13 @@ export class GithubOrgEntityProvider implements EntityProvider { return `GithubOrgEntityProvider:${this.options.id}`; } + private getPageSizes(): GithubPageSizes { + return { + ...DEFAULT_PAGE_SIZES, + ...this.options.pageSizes, + }; + } + /** {@inheritdoc @backstage/plugin-catalog-node#EntityProvider.connect} */ async connect(connection: EntityProviderConnection) { this.connection = connection; @@ -231,16 +258,20 @@ export class GithubOrgEntityProvider implements EntityProvider { }); const { org } = parseGithubOrgUrl(this.options.orgUrl); + const pageSizes = this.getPageSizes(); const { users } = await getOrganizationUsers( client, org, tokenType, this.options.userTransformer, + pageSizes, + this.options.excludeSuspendedUsers, ); const { teams } = await getOrganizationTeams( client, org, this.options.teamTransformer, + pageSizes, ); if (areGroupEntities(teams)) { @@ -352,6 +383,7 @@ export class GithubOrgEntityProvider implements EntityProvider { }); const { org } = parseGithubOrgUrl(this.options.orgUrl); + const pageSizes = this.getPageSizes(); const { team } = await getOrganizationTeam( client, org, @@ -364,6 +396,8 @@ export class GithubOrgEntityProvider implements EntityProvider { org, tokenType, this.options.userTransformer, + pageSizes, + this.options.excludeSuspendedUsers, ); if (!isGroupEntity(team)) { @@ -380,6 +414,7 @@ export class GithubOrgEntityProvider implements EntityProvider { org, usersToRebuild.map(u => u.metadata.name), this.options.teamTransformer, + pageSizes, ); if (areGroupEntities(teams)) { @@ -443,6 +478,7 @@ export class GithubOrgEntityProvider implements EntityProvider { }); const { org } = parseGithubOrgUrl(this.options.orgUrl); + const pageSizes = this.getPageSizes(); const { team } = await getOrganizationTeam( client, org, @@ -455,6 +491,8 @@ export class GithubOrgEntityProvider implements EntityProvider { org, tokenType, this.options.userTransformer, + pageSizes, + this.options.excludeSuspendedUsers, ); const usersToRebuild = users.filter(u => u.metadata.name === userLogin); @@ -464,6 +502,7 @@ export class GithubOrgEntityProvider implements EntityProvider { org, [userLogin], this.options.teamTransformer, + pageSizes, ); // we include group because the removed event need to update the old group too @@ -548,7 +587,13 @@ export class GithubOrgEntityProvider implements EntityProvider { const userTransformer = this.options.userTransformer || defaultUserTransformer; - const { name, avatar_url: avatarUrl, email, login } = event.membership.user; + const { + name, + avatar_url: avatarUrl, + email, + login, + node_id, + } = event.membership.user; const org = event.organization.login; const { headers } = await this.credentialsProvider.getCredentials({ url: this.options.orgUrl, @@ -564,6 +609,7 @@ export class GithubOrgEntityProvider implements EntityProvider { avatarUrl, login, email: email || undefined, + id: node_id, // we don't have this information in the event, so the refresh will handle that for us organizationVerifiedDomainEmails: [], }, diff --git a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md index 642a4cdc9b..46bb0f16f0 100644 --- a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-catalog-backend-module-gitlab-org +## 0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-backend-module-gitlab@0.7.6 + - @backstage/plugin-catalog-node@1.20.1 + +## 0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/plugin-catalog-backend-module-gitlab@0.7.6-next.1 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## 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 + ## 0.2.15 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab-org/package.json b/plugins/catalog-backend-module-gitlab-org/package.json index 729ca171b4..3e20ec5523 100644 --- a/plugins/catalog-backend-module-gitlab-org/package.json +++ b/plugins/catalog-backend-module-gitlab-org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gitlab-org", - "version": "0.2.15", + "version": "0.2.16", "description": "The gitlab-org backend module for the catalog plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-gitlab/CHANGELOG.md b/plugins/catalog-backend-module-gitlab/CHANGELOG.md index f0254bba71..4be1dc27ae 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,44 @@ # @backstage/plugin-catalog-backend-module-gitlab +## 0.7.6 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-defaults@0.14.0 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## 0.7.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## 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 + ## 0.7.5 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index a045262440..e9f315fb1a 100644 --- a/plugins/catalog-backend-module-gitlab/package.json +++ b/plugins/catalog-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gitlab", - "version": "0.7.5", + "version": "0.7.6", "description": "A Backstage catalog backend module that helps integrate towards GitLab", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md index cc6488d03a..aec78e3604 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md +++ b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md @@ -1,5 +1,52 @@ # @backstage/plugin-catalog-backend-module-incremental-ingestion +## 0.7.7 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- fb029b6: Updated luxon types +- Updated dependencies + - @backstage/backend-defaults@0.14.0 + - @backstage/plugin-catalog-backend@3.3.0 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## 0.7.7-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- fb029b6: Updated luxon types +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/plugin-catalog-backend@3.2.1-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-permission-common@0.9.3 + +## 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 + ## 0.7.6 ### Patch Changes diff --git a/plugins/catalog-backend-module-incremental-ingestion/package.json b/plugins/catalog-backend-module-incremental-ingestion/package.json index 2e4d56c844..0ee8e47652 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/package.json +++ b/plugins/catalog-backend-module-incremental-ingestion/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-incremental-ingestion", - "version": "0.7.6", + "version": "0.7.7", "description": "An entity provider for streaming large asset sources into the catalog", "backstage": { "role": "backend-plugin-module", @@ -58,7 +58,7 @@ "@backstage/plugin-permission-common": "workspace:^", "@backstage/types": "workspace:^", "@opentelemetry/api": "^1.9.0", - "express": "^4.17.1", + "express": "^4.22.0", "express-promise-router": "^4.1.0", "knex": "^3.0.0", "luxon": "^3.0.0", @@ -67,7 +67,6 @@ "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@types/express": "^4.17.6", - "@types/luxon": "^3.0.0" + "@types/express": "^4.17.6" } } diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md index 9fda9e4ba6..6bcb9ea561 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,39 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.12.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## 0.12.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## 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 + ## 0.12.0 ### Minor Changes diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index 1b745dc94a..52bce2aae7 100644 --- a/plugins/catalog-backend-module-ldap/package.json +++ b/plugins/catalog-backend-module-ldap/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-ldap", - "version": "0.12.0", + "version": "0.12.1", "description": "A Backstage catalog backend module that helps integrate towards LDAP", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-logs/CHANGELOG.md b/plugins/catalog-backend-module-logs/CHANGELOG.md index c50fa18139..6499dc340c 100644 --- a/plugins/catalog-backend-module-logs/CHANGELOG.md +++ b/plugins/catalog-backend-module-logs/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-catalog-backend-module-logs +## 0.1.17 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.3.0 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## 0.1.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.2.1-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 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 + ## 0.1.16 ### Patch Changes diff --git a/plugins/catalog-backend-module-logs/package.json b/plugins/catalog-backend-module-logs/package.json index f860a66a88..7202952210 100644 --- a/plugins/catalog-backend-module-logs/package.json +++ b/plugins/catalog-backend-module-logs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-logs", - "version": "0.1.16", + "version": "0.1.17", "description": "A module that subscribes to catalog related events and logs them.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-msgraph/CHANGELOG.md b/plugins/catalog-backend-module-msgraph/CHANGELOG.md index 405c2675ed..6577c69166 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-catalog-backend-module-msgraph +## 0.8.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## 0.8.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## 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 + ## 0.8.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index 0cb6ba54ce..0dddba19f7 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-msgraph", - "version": "0.8.2", + "version": "0.8.3", "description": "A Backstage catalog backend module that helps integrate towards Microsoft Graph", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-openapi/CHANGELOG.md b/plugins/catalog-backend-module-openapi/CHANGELOG.md index bb7b64bcd6..031087fe0e 100644 --- a/plugins/catalog-backend-module-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-openapi/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-catalog-backend-module-openapi +## 0.2.17 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## 0.2.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## 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 + ## 0.2.16 ### Patch Changes diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json index 6a43946202..fa9eddcf3a 100644 --- a/plugins/catalog-backend-module-openapi/package.json +++ b/plugins/catalog-backend-module-openapi/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-openapi", - "version": "0.2.16", + "version": "0.2.17", "description": "A Backstage catalog backend module that helps with OpenAPI specifications", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md index c61b2c8e39..c270f87cb5 100644 --- a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md +++ b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/plugin-catalog-backend-module-puppetdb +## 0.2.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## 0.2.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## 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 + ## 0.2.16 ### Patch Changes diff --git a/plugins/catalog-backend-module-puppetdb/package.json b/plugins/catalog-backend-module-puppetdb/package.json index b3f1b09a09..f2cd6115d1 100644 --- a/plugins/catalog-backend-module-puppetdb/package.json +++ b/plugins/catalog-backend-module-puppetdb/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-puppetdb", - "version": "0.2.16", + "version": "0.2.17", "description": "A Backstage catalog backend module that helps integrate towards PuppetDB", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md index 4ae73ca96f..f1e46d21fc 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md +++ b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-catalog-backend-module-scaffolder-entity-model +## 0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-scaffolder-common@1.7.4 + +## 0.2.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + +## 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 + ## 0.2.14 ### Patch Changes diff --git a/plugins/catalog-backend-module-scaffolder-entity-model/package.json b/plugins/catalog-backend-module-scaffolder-entity-model/package.json index 24cf91fe25..49d98fd6fc 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/package.json +++ b/plugins/catalog-backend-module-scaffolder-entity-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-scaffolder-entity-model", - "version": "0.2.14", + "version": "0.2.15", "description": "Adds support for the scaffolder specific entity model (e.g. the Template kind) to the catalog backend plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md index 7560631e59..32dde9548a 100644 --- a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md +++ b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md @@ -1,5 +1,48 @@ # @backstage/plugin-catalog-backend-module-unprocessed +## 0.6.7 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.12 + - @backstage/plugin-catalog-node@1.20.1 + +## 0.6.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.12-next.0 + - @backstage/plugin-permission-common@0.9.3 + +## 0.6.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.12-next.0 + +## 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 + ## 0.6.6 ### Patch Changes diff --git a/plugins/catalog-backend-module-unprocessed/package.json b/plugins/catalog-backend-module-unprocessed/package.json index 0c9ed6f33c..b735e7a13a 100644 --- a/plugins/catalog-backend-module-unprocessed/package.json +++ b/plugins/catalog-backend-module-unprocessed/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-unprocessed", - "version": "0.6.6", + "version": "0.6.7", "description": "Backstage Catalog module to view unprocessed entities", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index a48267dc3e..11c45546e9 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,62 @@ # @backstage/plugin-catalog-backend +## 3.3.0 + +### Minor Changes + +- dce1824: Added `ActionsRegistry` actions for `register-entity` and `unregister-entity` + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 8101ec1: Fixed default refresh service to go through the whole ancestry of the entity. +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-openapi-utils@0.6.4 + - @backstage/plugin-events-node@0.4.18 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## 3.2.1-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.4-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-permission-common@0.9.3 + +## 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 + ## 3.2.0 ### Minor Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 67f5eaf0ba..1176fda21b 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "3.2.0", + "version": "3.3.0", "description": "The Backstage backend plugin that provides the Backstage catalog", "backstage": { "role": "backend-plugin", @@ -77,7 +77,7 @@ "@opentelemetry/api": "^1.9.0", "codeowners-utils": "^1.0.2", "core-js": "^3.6.5", - "express": "^4.17.1", + "express": "^4.22.0", "fast-json-stable-stringify": "^2.1.0", "fs-extra": "^11.2.0", "git-url-parse": "^15.0.0", diff --git a/plugins/catalog-backend/src/actions/createRegisterCatalogEntitiesAction.test.ts b/plugins/catalog-backend/src/actions/createRegisterCatalogEntitiesAction.test.ts new file mode 100644 index 0000000000..31a89a31f3 --- /dev/null +++ b/plugins/catalog-backend/src/actions/createRegisterCatalogEntitiesAction.test.ts @@ -0,0 +1,105 @@ +/* + * 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 { createRegisterCatalogEntitiesAction } from './createRegisterCatalogEntitiesAction'; +import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; +import { actionsRegistryServiceMock } from '@backstage/backend-test-utils/alpha'; +import { ForwardedError } from '@backstage/errors'; + +describe('createRegisterCatalogEntitiesAction', () => { + it('should successfully register a catalog location with a valid URL', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockCatalog = catalogServiceMock(); + + const mockLocationId = 'test-location-id-1234'; + mockCatalog.addLocation = jest.fn().mockResolvedValue({ + location: { + id: mockLocationId, + type: 'url', + target: 'https://github.com/example/repo/blob/main/catalog-info.yaml', + }, + entities: [], + exists: false, + }); + + createRegisterCatalogEntitiesAction({ + catalog: mockCatalog, + actionsRegistry: mockActionsRegistry, + }); + + const result = await mockActionsRegistry.invoke({ + id: 'test:register-entity', + input: { + locationUrl: + 'https://github.com/example/repo/blob/main/catalog-info.yaml', + }, + }); + + expect(result.output).toEqual({ + locationId: mockLocationId, + }); + expect(mockCatalog.addLocation).toHaveBeenCalledWith( + { + type: 'url', + target: 'https://github.com/example/repo/blob/main/catalog-info.yaml', + }, + expect.objectContaining({ + credentials: expect.any(Object), + }), + ); + }); + + it('should throw an error if locationUrl is not a valid URL', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockCatalog = catalogServiceMock(); + + createRegisterCatalogEntitiesAction({ + catalog: mockCatalog, + actionsRegistry: mockActionsRegistry, + }); + + await expect( + mockActionsRegistry.invoke({ + id: 'test:register-entity', + input: { locationUrl: 'not-a-valid-url' }, + }), + ).rejects.toThrow('not-a-valid-url is an invalid URL'); + }); + + it('should throw a ForwardedError if catalog.addLocation throws an error', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockCatalog = catalogServiceMock(); + + const errorMessage = 'Failed to add location'; + mockCatalog.addLocation = jest + .fn() + .mockRejectedValue(new Error(errorMessage)); + + createRegisterCatalogEntitiesAction({ + catalog: mockCatalog, + actionsRegistry: mockActionsRegistry, + }); + + await expect( + mockActionsRegistry.invoke({ + id: 'test:register-entity', + input: { + locationUrl: + 'https://github.com/example/repo/blob/main/catalog-info.yaml', + }, + }), + ).rejects.toThrow(ForwardedError); + }); +}); diff --git a/plugins/catalog-backend/src/actions/createRegisterCatalogEntitiesAction.ts b/plugins/catalog-backend/src/actions/createRegisterCatalogEntitiesAction.ts new file mode 100644 index 0000000000..c521672ae1 --- /dev/null +++ b/plugins/catalog-backend/src/actions/createRegisterCatalogEntitiesAction.ts @@ -0,0 +1,89 @@ +/* + * 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 { ActionsRegistryService } from '@backstage/backend-plugin-api/alpha'; +import { InputError } from '@backstage/errors'; +import { CatalogService } from '@backstage/plugin-catalog-node'; + +const isValidUrl = (url: string) => { + try { + // eslint-disable-next-line no-new + new URL(url); + return true; + } catch (error) { + return false; + } +}; + +export const createRegisterCatalogEntitiesAction = ({ + catalog, + actionsRegistry, +}: { + catalog: CatalogService; + actionsRegistry: ActionsRegistryService; +}) => { + actionsRegistry.register({ + name: 'register-entity', + title: 'Register entity in the Catalog', + attributes: { + destructive: false, + readOnly: false, + idempotent: false, + }, + description: `Registers one or more entities in the Backstage catalog by creating a Location entity that points to a remote catalog-info.yaml file. + +This action is similar to the "Register existing component" flow in the Backstage UI, where you provide a URL to a catalog-info.yaml file describing catalog entities such as Components, Systems, Resources, APIs, Users, and Groups. The action will create a new Location entity that references the provided file; all entities defined within that file will be imported into the catalog. + +A unique identifier (locationId) will be returned for the newly created Location. You can use this identifier in the future to unregister or delete the Location and all entities it owns. +`, + schema: { + input: z => + z.object({ + locationUrl: z + .string() + .describe( + `URL reference to the catalog-info.yaml file that describes the entity to register. For example: https://github.com/backstage/demo/blob/master/catalog-info.yaml`, + ), + }), + output: z => + z.object({ + locationId: z + .string() + .describe('The ID of the entity that was registered'), + }), + }, + action: async ({ input, credentials }) => { + if (!isValidUrl(input.locationUrl)) { + throw new InputError(`${input.locationUrl} is an invalid URL`); + } + + const result = await catalog.addLocation( + { + type: 'url', + target: input.locationUrl, + }, + { + credentials, + }, + ); + + return { + output: { + locationId: result.location.id, + }, + }; + }, + }); +}; diff --git a/plugins/catalog-backend/src/actions/createUnregisterCatalogEntitiesAction.test.ts b/plugins/catalog-backend/src/actions/createUnregisterCatalogEntitiesAction.test.ts new file mode 100644 index 0000000000..492a427630 --- /dev/null +++ b/plugins/catalog-backend/src/actions/createUnregisterCatalogEntitiesAction.test.ts @@ -0,0 +1,274 @@ +/* + * 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 { createUnregisterCatalogEntitiesAction } from './createUnregisterCatalogEntitiesAction'; +import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; +import { actionsRegistryServiceMock } from '@backstage/backend-test-utils/alpha'; + +describe('createUnregisterCatalogEntitiesAction', () => { + describe('with locationId', () => { + it('should successfully unregister a catalog location with a valid locationId', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockCatalog = catalogServiceMock(); + + mockCatalog.removeLocationById = jest.fn().mockResolvedValue(undefined); + + createUnregisterCatalogEntitiesAction({ + catalog: mockCatalog, + actionsRegistry: mockActionsRegistry, + }); + + const result = await mockActionsRegistry.invoke({ + id: 'test:unregister-entity', + input: { type: { locationId: 'test-location-id-1234' } }, + }); + + expect(result.output).toEqual({}); + expect(mockCatalog.removeLocationById).toHaveBeenCalledWith( + 'test-location-id-1234', + expect.objectContaining({ + credentials: expect.any(Object), + }), + ); + }); + + it('should throw an error if catalog.removeLocationById throws an error', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockCatalog = catalogServiceMock(); + + const errorMessage = 'Failed to remove location'; + mockCatalog.removeLocationById = jest + .fn() + .mockRejectedValue(new Error(errorMessage)); + + createUnregisterCatalogEntitiesAction({ + catalog: mockCatalog, + actionsRegistry: mockActionsRegistry, + }); + + await expect( + mockActionsRegistry.invoke({ + id: 'test:unregister-entity', + input: { type: { locationId: 'test-location-id-1234' } }, + }), + ).rejects.toThrow(errorMessage); + }); + }); + + describe('with locationUrl', () => { + it('should successfully unregister a catalog location with a valid locationUrl', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockCatalog = catalogServiceMock(); + + const locationUrl = + 'https://github.com/backstage/demo/blob/master/catalog-info.yaml'; + + mockCatalog.getLocations = jest.fn().mockResolvedValue({ + items: [ + { id: 'location-id-1', target: locationUrl }, + { id: 'location-id-2', target: 'https://other-url.com/catalog.yaml' }, + ], + }); + mockCatalog.removeLocationById = jest.fn().mockResolvedValue(undefined); + + createUnregisterCatalogEntitiesAction({ + catalog: mockCatalog, + actionsRegistry: mockActionsRegistry, + }); + + const result = await mockActionsRegistry.invoke({ + id: 'test:unregister-entity', + input: { type: { locationUrl } }, + }); + + expect(result.output).toEqual({}); + expect(mockCatalog.getLocations).toHaveBeenCalledWith( + {}, + expect.objectContaining({ + credentials: expect.any(Object), + }), + ); + expect(mockCatalog.removeLocationById).toHaveBeenCalledTimes(1); + expect(mockCatalog.removeLocationById).toHaveBeenCalledWith( + 'location-id-1', + expect.objectContaining({ + credentials: expect.any(Object), + }), + ); + }); + + it('should match locationUrl case-insensitively', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockCatalog = catalogServiceMock(); + + mockCatalog.getLocations = jest.fn().mockResolvedValue({ + items: [ + { + id: 'location-id-1', + target: + 'https://github.com/Backstage/Demo/blob/master/catalog-info.yaml', + }, + ], + }); + mockCatalog.removeLocationById = jest.fn().mockResolvedValue(undefined); + + createUnregisterCatalogEntitiesAction({ + catalog: mockCatalog, + actionsRegistry: mockActionsRegistry, + }); + + const result = await mockActionsRegistry.invoke({ + id: 'test:unregister-entity', + input: { + type: { + locationUrl: + 'https://github.com/backstage/demo/blob/master/catalog-info.yaml', + }, + }, + }); + + expect(result.output).toEqual({}); + expect(mockCatalog.removeLocationById).toHaveBeenCalledTimes(1); + expect(mockCatalog.removeLocationById).toHaveBeenCalledWith( + 'location-id-1', + expect.objectContaining({ + credentials: expect.any(Object), + }), + ); + }); + + it('should unregister multiple locations with the same URL', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockCatalog = catalogServiceMock(); + + const locationUrl = + 'https://github.com/backstage/demo/blob/master/catalog-info.yaml'; + + mockCatalog.getLocations = jest.fn().mockResolvedValue({ + items: [ + { id: 'location-id-1', target: locationUrl }, + { id: 'location-id-2', target: locationUrl }, + ], + }); + mockCatalog.removeLocationById = jest.fn().mockResolvedValue(undefined); + + createUnregisterCatalogEntitiesAction({ + catalog: mockCatalog, + actionsRegistry: mockActionsRegistry, + }); + + const result = await mockActionsRegistry.invoke({ + id: 'test:unregister-entity', + input: { type: { locationUrl } }, + }); + + expect(result.output).toEqual({}); + expect(mockCatalog.removeLocationById).toHaveBeenCalledTimes(2); + expect(mockCatalog.removeLocationById).toHaveBeenNthCalledWith( + 1, + 'location-id-1', + expect.objectContaining({ + credentials: expect.any(Object), + }), + ); + expect(mockCatalog.removeLocationById).toHaveBeenNthCalledWith( + 2, + 'location-id-2', + expect.objectContaining({ + credentials: expect.any(Object), + }), + ); + }); + + it('should throw NotFoundError if no location matches the URL', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockCatalog = catalogServiceMock(); + + mockCatalog.getLocations = jest.fn().mockResolvedValue({ + items: [ + { id: 'location-id-1', target: 'https://other-url.com/catalog.yaml' }, + ], + }); + + createUnregisterCatalogEntitiesAction({ + catalog: mockCatalog, + actionsRegistry: mockActionsRegistry, + }); + + await expect( + mockActionsRegistry.invoke({ + id: 'test:unregister-entity', + input: { + type: { + locationUrl: + 'https://github.com/backstage/demo/blob/master/catalog-info.yaml', + }, + }, + }), + ).rejects.toThrow(/NotFoundError/); + }); + + it('should throw NotFoundError with descriptive message when location not found', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockCatalog = catalogServiceMock(); + + const locationUrl = + 'https://github.com/backstage/demo/blob/master/catalog-info.yaml'; + + mockCatalog.getLocations = jest.fn().mockResolvedValue({ + items: [], + }); + + createUnregisterCatalogEntitiesAction({ + catalog: mockCatalog, + actionsRegistry: mockActionsRegistry, + }); + + await expect( + mockActionsRegistry.invoke({ + id: 'test:unregister-entity', + input: { type: { locationUrl } }, + }), + ).rejects.toThrow(`Location with URL ${locationUrl} not found`); + }); + + it('should throw an error if catalog.getLocations throws an error', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockCatalog = catalogServiceMock(); + + const errorMessage = 'Failed to get locations'; + mockCatalog.getLocations = jest + .fn() + .mockRejectedValue(new Error(errorMessage)); + + createUnregisterCatalogEntitiesAction({ + catalog: mockCatalog, + actionsRegistry: mockActionsRegistry, + }); + + await expect( + mockActionsRegistry.invoke({ + id: 'test:unregister-entity', + input: { + type: { + locationUrl: + 'https://github.com/backstage/demo/blob/master/catalog-info.yaml', + }, + }, + }), + ).rejects.toThrow(errorMessage); + }); + }); +}); diff --git a/plugins/catalog-backend/src/actions/createUnregisterCatalogEntitiesAction.ts b/plugins/catalog-backend/src/actions/createUnregisterCatalogEntitiesAction.ts new file mode 100644 index 0000000000..542fc79ac5 --- /dev/null +++ b/plugins/catalog-backend/src/actions/createUnregisterCatalogEntitiesAction.ts @@ -0,0 +1,102 @@ +/* + * 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 { ActionsRegistryService } from '@backstage/backend-plugin-api/alpha'; +import { NotFoundError } from '@backstage/errors'; +import { CatalogService } from '@backstage/plugin-catalog-node'; + +export const createUnregisterCatalogEntitiesAction = ({ + catalog, + actionsRegistry, +}: { + catalog: CatalogService; + actionsRegistry: ActionsRegistryService; +}) => { + actionsRegistry.register({ + name: 'unregister-entity', + title: 'Unregister entity from the Catalog', + attributes: { + destructive: true, + readOnly: false, + idempotent: true, + }, + description: `Unregisters a Location entity and all entities it owns from the Backstage catalog. + +This action is similar to the "Unregister location" function in the Backstage UI, where you provide the unique identifier (locationId) of a Location entity. Alternatively, you can provide the URL used to register the location. The action will remove the specified Location from the catalog as well as all entities that were created when the Location was imported. + +Once completed, all entities associated with the Location will be deleted from the catalog. +`, + schema: { + input: z => + z + .object({ + type: z.union([ + z.object({ + locationId: z + .string() + .describe(`Location ID of the Entity to unregister`), + }), + z.object({ + locationUrl: z + .string() + .describe( + `URL of the catalog-info.yaml file to unregister for example: https://github.com/backstage/demo/blob/master/catalog-info.yaml`, + ), + }), + ]), + }) + .describe( + 'The type to the unregister-entity action. Either locationId or locationUrl must be provided.', + ), + output: z => z.object({}), + }, + action: async ({ input: { type }, credentials }) => { + if ('locationId' in type) { + await catalog.removeLocationById(type.locationId, { + credentials, + }); + } else { + const locations = await catalog + .getLocations( + {}, + { + credentials, + }, + ) + .then(response => + response.items.filter( + location => + location.target.toLowerCase() === + type.locationUrl.toLowerCase(), + ), + ); + + if (locations.length === 0) { + throw new NotFoundError( + `Location with URL ${type.locationUrl} not found`, + ); + } + + for (const location of locations) { + await catalog.removeLocationById(location.id, { + credentials, + }); + } + } + + return { output: {} }; + }, + }); +}; diff --git a/plugins/catalog-backend/src/actions/index.ts b/plugins/catalog-backend/src/actions/index.ts index c53f956f62..5940bc32f1 100644 --- a/plugins/catalog-backend/src/actions/index.ts +++ b/plugins/catalog-backend/src/actions/index.ts @@ -17,6 +17,8 @@ import { ActionsRegistryService } from '@backstage/backend-plugin-api/alpha'; import { CatalogService } from '@backstage/plugin-catalog-node'; import { createGetCatalogEntityAction } from './createGetCatalogEntityAction.ts'; import { createValidateEntityAction } from './createValidateEntityAction.ts'; +import { createRegisterCatalogEntitiesAction } from './createRegisterCatalogEntitiesAction.ts'; +import { createUnregisterCatalogEntitiesAction } from './createUnregisterCatalogEntitiesAction.ts'; export const createCatalogActions = (options: { actionsRegistry: ActionsRegistryService; @@ -24,4 +26,6 @@ export const createCatalogActions = (options: { }) => { createGetCatalogEntityAction(options); createValidateEntityAction(options); + createRegisterCatalogEntitiesAction(options); + createUnregisterCatalogEntitiesAction(options); }; diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts index e9d00f3451..7012c2e505 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts @@ -1577,10 +1577,11 @@ describe('DefaultEntitiesCatalog', () => { totalItems: 0, items: { type: 'raw', - entities: expect.objectContaining({ length: 10 }), + entities: expect.any(Array), }, pageInfo: { nextCursor: expect.anything() }, }); + expect(response.items.entities).toHaveLength(10); response = await catalog.queryEntities({ ...request, cursor: response.pageInfo.nextCursor!, @@ -1589,10 +1590,11 @@ describe('DefaultEntitiesCatalog', () => { totalItems: 0, items: { type: 'raw', - entities: expect.objectContaining({ length: 5 }), + entities: expect.any(Array), }, pageInfo: { prevCursor: expect.anything() }, }); + expect(response.items.entities).toHaveLength(5); }, ); diff --git a/plugins/catalog-backend/src/service/DefaultRefreshService.ts b/plugins/catalog-backend/src/service/DefaultRefreshService.ts index 99b78f900b..45a8f27c6e 100644 --- a/plugins/catalog-backend/src/service/DefaultRefreshService.ts +++ b/plugins/catalog-backend/src/service/DefaultRefreshService.ts @@ -29,20 +29,17 @@ export class DefaultRefreshService implements RefreshService { const { entityRefs } = await this.database.listAncestors(tx, { entityRef: options.entityRef, }); - const locationAncestor = entityRefs.find(ref => + + const locationAncestors = entityRefs.filter(ref => ref.startsWith('location:'), ); // TODO: Refreshes are currently scheduled(as soon as possible) for execution and will therefore happen in the future. // There's room for improvements here where the refresh could potentially hang or return an ID so that the user can check progress. - if (locationAncestor) { - await this.database.refresh(tx, { - entityRef: locationAncestor, - }); + for (const locationAncestor of locationAncestors) { + await this.database.refresh(tx, { entityRef: locationAncestor }); } - await this.database.refresh(tx, { - entityRef: options.entityRef, - }); + await this.database.refresh(tx, { entityRef: options.entityRef }); }); } } diff --git a/plugins/catalog-backend/src/service/response/write.test.ts b/plugins/catalog-backend/src/service/response/write.test.ts index d60d1c3526..25aaa33aa1 100644 --- a/plugins/catalog-backend/src/service/response/write.test.ts +++ b/plugins/catalog-backend/src/service/response/write.test.ts @@ -327,9 +327,10 @@ describe('writeEntitiesResponse', () => { expect(res.header['content-length']).not.toBeDefined(); expect(res.body).toEqual({ page: 1, - items: expect.objectContaining({ length: 300 }), + items: expect.any(Array), totalItems: 1337, }); + expect(res.body.items).toHaveLength(300); }); }); @@ -438,9 +439,10 @@ describe('writeEntitiesResponse', () => { expect(res.header['content-length']).toBeDefined(); expect(res.body).toEqual({ page: 1, - items: expect.objectContaining({ length: 300 }), + items: expect.any(Array), totalItems: 1337, }); + expect(res.body.items).toHaveLength(300); }); }); }); diff --git a/plugins/catalog-graph/CHANGELOG.md b/plugins/catalog-graph/CHANGELOG.md index 8436760d17..b97ea7d6a2 100644 --- a/plugins/catalog-graph/CHANGELOG.md +++ b/plugins/catalog-graph/CHANGELOG.md @@ -1,5 +1,47 @@ # @backstage/plugin-catalog-graph +## 0.5.4 + +### Patch Changes + +- a67c144: Fixed zoom out description (replace `&` with &) +- f3f84f1: Minor extension type updates after frontend API bump +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + +## 0.5.4-next.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/types@1.2.2 + +## 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 + ## 0.5.3 ### Patch Changes diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index 49f4a9836b..64b4331356 100644 --- a/plugins/catalog-graph/package.json +++ b/plugins/catalog-graph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-graph", - "version": "0.5.3", + "version": "0.5.4", "backstage": { "role": "frontend-plugin", "pluginId": "catalog-graph", @@ -51,7 +51,6 @@ "dependencies": { "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", - "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", diff --git a/plugins/catalog-graph/report-alpha.api.md b/plugins/catalog-graph/report-alpha.api.md index 285b8b3119..2ce85431e8 100644 --- a/plugins/catalog-graph/report-alpha.api.md +++ b/plugins/catalog-graph/report-alpha.api.md @@ -11,12 +11,12 @@ import { EntityCardType } from '@backstage/plugin-catalog-react/alpha'; import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; -import { ExtensionInput } from '@backstage/frontend-plugin-api'; -import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; +import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; -import { RouteRef } from '@backstage/frontend-plugin-api'; +import { RouteRef } from '@backstage/core-plugin-api'; +import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; import { TranslationRef } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) @@ -30,7 +30,7 @@ export const catalogGraphTranslationRef: TranslationRef< readonly 'catalogGraphPage.supportButtonDescription': 'Start tracking your component in by adding it to the software catalog.'; readonly 'catalogGraphPage.simplifiedSwitchLabel': 'Simplified'; readonly 'catalogGraphPage.mergeRelationsSwitchLabel': 'Merge relations'; - readonly 'catalogGraphPage.zoomOutDescription': 'Use pinch & zoom to move around the diagram. Click to change active node, shift click to navigate to entity.'; + readonly 'catalogGraphPage.zoomOutDescription': 'Use pinch & zoom to move around the diagram. Click to change active node, shift click to navigate to entity.'; readonly 'catalogGraphPage.curveFilter.title': 'Curve'; readonly 'catalogGraphPage.curveFilter.curveStepBefore': 'Step Before'; readonly 'catalogGraphPage.curveFilter.curveMonotoneX': 'Monotone X'; @@ -53,11 +53,14 @@ const _default: OverridableFrontendPlugin< catalogGraph: RouteRef; }, { - catalogEntity: ExternalRouteRef<{ - name: string; - kind: string; - namespace: string; - }>; + catalogEntity: ExternalRouteRef< + { + name: string; + kind: string; + namespace: string; + }, + true + >; }, { 'api:catalog-graph': OverridableExtensionDefinition<{ @@ -88,7 +91,6 @@ const _default: OverridableFrontendPlugin< curve: 'curveStepBefore' | 'curveMonotoneX' | undefined; title: string | undefined; height: number | undefined; - } & { filter: EntityPredicate | undefined; type: 'content' | 'summary' | 'info' | undefined; }; @@ -104,7 +106,6 @@ const _default: OverridableFrontendPlugin< mergeRelations?: boolean | undefined; relationPairs?: [string, string][] | undefined; unidirectional?: boolean | undefined; - } & { filter?: EntityPredicate | undefined; type?: 'content' | 'summary' | 'info' | undefined; }; @@ -131,15 +132,7 @@ const _default: OverridableFrontendPlugin< optional: true; } >; - inputs: { - [x: string]: ExtensionInput< - ExtensionDataRef, - { - singleton: boolean; - optional: boolean; - } - >; - }; + inputs: {}; kind: 'entity-card'; name: 'relations'; params: { @@ -163,7 +156,6 @@ const _default: OverridableFrontendPlugin< relations: string[] | undefined; relationPairs: [string, string][] | undefined; zoom: 'disabled' | 'enabled' | 'enable-on-click' | undefined; - } & { path: string | undefined; }; configInput: { @@ -180,35 +172,26 @@ const _default: OverridableFrontendPlugin< selectedRelations?: string[] | undefined; selectedKinds?: string[] | undefined; showFilters?: boolean | undefined; - } & { path?: string | undefined; }; output: | ExtensionDataRef | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; } >; - inputs: { - [x: string]: ExtensionInput< - ExtensionDataRef, - { - singleton: boolean; - optional: boolean; - } - >; - }; + inputs: {}; kind: 'page'; name: undefined; params: { defaultPath?: [Error: `Use the 'path' param instead`]; path: string; loader: () => Promise; - routeRef?: RouteRef; + routeRef?: RouteRef_2; }; }>; } diff --git a/plugins/catalog-graph/report.api.md b/plugins/catalog-graph/report.api.md index d9342dbf36..8d3b8f2cc8 100644 --- a/plugins/catalog-graph/report.api.md +++ b/plugins/catalog-graph/report.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 { ApiRef } from '@backstage/core-plugin-api'; +import { ApiRef } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { CompoundEntityRef } from '@backstage/catalog-model'; import { DependencyGraphTypes } from '@backstage/core-components'; diff --git a/plugins/catalog-graph/src/alpha.tsx b/plugins/catalog-graph/src/alpha.tsx index 9a7f15e553..a2a8c7eb5e 100644 --- a/plugins/catalog-graph/src/alpha.tsx +++ b/plugins/catalog-graph/src/alpha.tsx @@ -19,10 +19,6 @@ import { createFrontendPlugin, PageBlueprint, } from '@backstage/frontend-plugin-api'; -import { - compatWrapper, - convertLegacyRouteRef, -} from '@backstage/core-compat-api'; import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha'; import { catalogGraphRouteRef, catalogEntityRouteRef } from './routes'; import { @@ -53,9 +49,9 @@ const CatalogGraphEntityCard = EntityCardBlueprint.makeWithOverrides({ factory(originalFactory, { config }) { return originalFactory({ loader: async () => - import('./components/CatalogGraphCard').then(m => - compatWrapper(), - ), + import('./components/CatalogGraphCard').then(m => ( + + )), }); }, }); @@ -81,11 +77,11 @@ const CatalogGraphPage = PageBlueprint.makeWithOverrides({ factory(originalFactory, { config }) { return originalFactory({ path: '/catalog-graph', - routeRef: convertLegacyRouteRef(catalogGraphRouteRef), + routeRef: catalogGraphRouteRef, loader: () => - import('./components/CatalogGraphPage').then(m => - compatWrapper(), - ), + import('./components/CatalogGraphPage').then(m => ( + + )), }); }, }); @@ -103,10 +99,10 @@ export default createFrontendPlugin({ pluginId: 'catalog-graph', info: { packageJson: () => import('../package.json') }, routes: { - catalogGraph: convertLegacyRouteRef(catalogGraphRouteRef), + catalogGraph: catalogGraphRouteRef, }, externalRoutes: { - catalogEntity: convertLegacyRouteRef(catalogEntityRouteRef), + catalogEntity: catalogEntityRouteRef, }, extensions: [CatalogGraphPage, CatalogGraphEntityCard, CatalogGraphApi], }); diff --git a/plugins/catalog-graph/src/translation.ts b/plugins/catalog-graph/src/translation.ts index 33c4ac2d3a..84b76f965d 100644 --- a/plugins/catalog-graph/src/translation.ts +++ b/plugins/catalog-graph/src/translation.ts @@ -31,7 +31,7 @@ export const catalogGraphTranslationRef = createTranslationRef({ simplifiedSwitchLabel: 'Simplified', mergeRelationsSwitchLabel: 'Merge relations', zoomOutDescription: - 'Use pinch & zoom to move around the diagram. Click to change active node, shift click to navigate to entity.', + 'Use pinch & zoom to move around the diagram. Click to change active node, shift click to navigate to entity.', curveFilter: { title: 'Curve', curveMonotoneX: 'Monotone X', diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index 336ef7af15..9e25364a9c 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-catalog-import +## 0.13.8 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-permission-react@0.4.39 + +## 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 + ## 0.13.7 ### Patch Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 6d9b3dd66d..e9d25876af 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-import", - "version": "0.13.7", + "version": "0.13.8", "description": "A Backstage plugin the helps you import entities into your catalog", "backstage": { "role": "frontend-plugin", @@ -57,7 +57,6 @@ "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", - "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", diff --git a/plugins/catalog-import/report-alpha.api.md b/plugins/catalog-import/report-alpha.api.md index 5dfa90388a..935c3711ab 100644 --- a/plugins/catalog-import/report-alpha.api.md +++ b/plugins/catalog-import/report-alpha.api.md @@ -3,16 +3,17 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { AnyApiFactory } from '@backstage/core-plugin-api'; +import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; -import { ApiFactory } from '@backstage/core-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; -import { RouteRef } from '@backstage/frontend-plugin-api'; -import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +import { RouteRef } from '@backstage/core-plugin-api'; +import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) export const catalogImportTranslationRef: TranslationRef< @@ -122,7 +123,7 @@ const _default: OverridableFrontendPlugin< | ExtensionDataRef | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; @@ -133,7 +134,7 @@ const _default: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; loader: () => Promise; - routeRef?: RouteRef; + routeRef?: RouteRef_2; }; }>; } diff --git a/plugins/catalog-import/report.api.md b/plugins/catalog-import/report.api.md index ed7d2f3347..238231e498 100644 --- a/plugins/catalog-import/report.api.md +++ b/plugins/catalog-import/report.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 { ApiRef } from '@backstage/core-plugin-api'; +import { ApiRef } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { CatalogApi } from '@backstage/catalog-client'; import { catalogImportTranslationRef } from '@backstage/plugin-catalog-import/alpha'; diff --git a/plugins/catalog-import/src/alpha.tsx b/plugins/catalog-import/src/alpha.tsx index 2d9e5deaba..a00eb39988 100644 --- a/plugins/catalog-import/src/alpha.tsx +++ b/plugins/catalog-import/src/alpha.tsx @@ -19,10 +19,6 @@ import { discoveryApiRef, fetchApiRef, } from '@backstage/core-plugin-api'; -import { - compatWrapper, - convertLegacyRouteRef, -} from '@backstage/core-compat-api'; import { createFrontendPlugin, PageBlueprint, @@ -45,15 +41,13 @@ export * from './translation'; const catalogImportPage = PageBlueprint.make({ params: { path: '/catalog-import', - routeRef: convertLegacyRouteRef(rootRouteRef), + routeRef: rootRouteRef, loader: () => - import('./components/ImportPage').then(m => - compatWrapper( - - - , - ), - ), + import('./components/ImportPage').then(m => ( + + + + )), }, }); @@ -94,7 +88,7 @@ export default createFrontendPlugin({ info: { packageJson: () => import('../package.json') }, extensions: [catalogImportApi, catalogImportPage], routes: { - importPage: convertLegacyRouteRef(rootRouteRef), + importPage: rootRouteRef, }, }); diff --git a/plugins/catalog-node/CHANGELOG.md b/plugins/catalog-node/CHANGELOG.md index 2bda2e7fbc..112e1cfca2 100644 --- a/plugins/catalog-node/CHANGELOG.md +++ b/plugins/catalog-node/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-catalog-node +## 1.20.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + +## 1.20.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-common@0.9.3 + +## 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 + ## 1.20.0 ### Minor Changes diff --git a/plugins/catalog-node/package.json b/plugins/catalog-node/package.json index 5f6aecaf30..befa4e9b7f 100644 --- a/plugins/catalog-node/package.json +++ b/plugins/catalog-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-node", - "version": "1.20.0", + "version": "1.20.1", "description": "The plugin-catalog-node module for @backstage/plugin-catalog-backend", "backstage": { "role": "node-library", diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index 4779ae878e..c66c5767ae 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,72 @@ # @backstage/plugin-catalog-react +## 1.21.4 + +### Patch Changes + +- 6d39141: Fixed an issue where `EntityOwnerPicker` failed to filter options when the input text contained uppercase characters. +- b3c0594: Use a versioned context for `useEntityList`, to better work with mixed `@backstage/plugin-catalog-react` versions. +- c51c901: $contains may have string value in an entity filter. Typescript type and Zod parser were not the same. +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/core-compat-api@0.5.5 + - @backstage/frontend-test-utils@0.4.2 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-permission-react@0.4.39 + +## 1.21.4-next.2 + +### Patch Changes + +- b3c0594: Use a versioned context for `useEntityList`, to better work with mixed `@backstage/plugin-catalog-react` versions. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-compat-api@0.5.5-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/frontend-test-utils@0.4.2-next.0 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-permission-react@0.4.39-next.0 + +## 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 + +## 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 + ## 1.21.3 ### Patch Changes diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index 15251f6c46..bfd4e11738 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-react", - "version": "1.21.3", + "version": "1.21.4", "description": "A frontend library that helps other Backstage plugins interact with the catalog", "backstage": { "role": "web-library", diff --git a/plugins/catalog-react/report-alpha.api.md b/plugins/catalog-react/report-alpha.api.md index 4b4a0a9a18..22ac105eeb 100644 --- a/plugins/catalog-react/report-alpha.api.md +++ b/plugins/catalog-react/report-alpha.api.md @@ -16,7 +16,7 @@ import { JSX as JSX_2 } from 'react'; import { ReactNode } from 'react'; import { ResourcePermission } from '@backstage/plugin-permission-common'; import { RouteRef } from '@backstage/frontend-plugin-api'; -import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; // @alpha export const CatalogFilterBlueprint: ExtensionBlueprint<{ @@ -112,8 +112,8 @@ export const catalogReactTranslationRef: TranslationRef< readonly 'entityTableColumnTitle.description': 'Description'; readonly 'entityTableColumnTitle.domain': 'Domain'; readonly 'entityTableColumnTitle.system': 'System'; - readonly 'entityTableColumnTitle.tags': 'Tags'; readonly 'entityTableColumnTitle.namespace': 'Namespace'; + readonly 'entityTableColumnTitle.tags': 'Tags'; readonly 'entityTableColumnTitle.lifecycle': 'Lifecycle'; readonly 'entityTableColumnTitle.owner': 'Owner'; readonly 'entityTableColumnTitle.targets': 'Targets'; @@ -549,7 +549,7 @@ export type EntityPredicateValue = $in: EntityPredicatePrimitive[]; } | { - $contains: EntityPredicateExpression; + $contains: EntityPredicate; }; // @alpha (undocumented) diff --git a/plugins/catalog-react/report.api.md b/plugins/catalog-react/report.api.md index d013199a00..14c832d545 100644 --- a/plugins/catalog-react/report.api.md +++ b/plugins/catalog-react/report.api.md @@ -3,7 +3,8 @@ > 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 { ApiRef } from '@backstage/frontend-plugin-api'; +import { ApiRef as ApiRef_2 } from '@backstage/core-plugin-api'; import { AutocompleteProps } from '@material-ui/lab/Autocomplete'; import { CATALOG_FILTER_EXISTS } from '@backstage/catalog-client'; import { CatalogApi } from '@backstage/catalog-client'; @@ -321,7 +322,7 @@ export const EntityLifecyclePicker: (props: { initialFilter?: string[]; }) => JSX_2.Element; -// @public +// @public @deprecated export const EntityListContext: Context< EntityListContextProps | undefined >; @@ -475,7 +476,7 @@ export interface EntityPresentationApi { } // @public -export const entityPresentationApiRef: ApiRef; +export const entityPresentationApiRef: ApiRef_2; // @public (undocumented) export const EntityProcessingStatusPicker: () => JSX_2.Element; @@ -774,7 +775,7 @@ export interface StarredEntitiesApi { } // @public -export const starredEntitiesApiRef: ApiRef; +export const starredEntitiesApiRef: ApiRef_2; // @public (undocumented) export const UnregisterEntityDialog: ( diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.test.tsx b/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.test.tsx index 3c9a020c93..af04673ad1 100644 --- a/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.test.tsx +++ b/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.test.tsx @@ -60,20 +60,75 @@ describe('EntityCardBlueprint', () => { { "anyOf": [ { - "type": [ - "string", - "number", - "boolean", - ], + "additionalProperties": { + "anyOf": [ + { + "type": [ + "string", + "number", + "boolean", + ], + }, + { + "additionalProperties": false, + "properties": { + "$exists": { + "type": "boolean", + }, + }, + "required": [ + "$exists", + ], + "type": "object", + }, + { + "additionalProperties": false, + "properties": { + "$in": { + "items": { + "$ref": "#/properties/filter/anyOf/1/anyOf/0/anyOf/0/additionalProperties/anyOf/0", + }, + "type": "array", + }, + }, + "required": [ + "$in", + ], + "type": "object", + }, + { + "additionalProperties": false, + "properties": { + "$contains": { + "$ref": "#/properties/filter/anyOf/1", + }, + }, + "required": [ + "$contains", + ], + "type": "object", + }, + ], + }, + "propertyNames": { + "pattern": "^(?!\\$).*$", + }, + "type": "object", }, { - "items": { - "$ref": "#/properties/filter/anyOf/1/anyOf/0/anyOf/0", + "additionalProperties": { + "not": {}, }, - "type": "array", + "propertyNames": { + "pattern": "^\\$", + }, + "type": "object", }, ], }, + { + "$ref": "#/properties/filter/anyOf/1/anyOf/0/anyOf/0/additionalProperties/anyOf/0", + }, { "additionalProperties": false, "properties": { @@ -116,58 +171,6 @@ describe('EntityCardBlueprint', () => { ], "type": "object", }, - { - "additionalProperties": { - "anyOf": [ - { - "$ref": "#/properties/filter/anyOf/1/anyOf/0", - }, - { - "additionalProperties": false, - "properties": { - "$exists": { - "type": "boolean", - }, - }, - "required": [ - "$exists", - ], - "type": "object", - }, - { - "additionalProperties": false, - "properties": { - "$in": { - "items": { - "$ref": "#/properties/filter/anyOf/1/anyOf/0/anyOf/0", - }, - "type": "array", - }, - }, - "required": [ - "$in", - ], - "type": "object", - }, - { - "additionalProperties": false, - "properties": { - "$contains": { - "$ref": "#/properties/filter/anyOf/1", - }, - }, - "required": [ - "$contains", - ], - "type": "object", - }, - ], - }, - "propertyNames": { - "pattern": "^(?!\\$).*$", - }, - "type": "object", - }, ], }, ], diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.test.tsx b/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.test.tsx index 729f15079f..e80834be92 100644 --- a/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.test.tsx +++ b/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.test.tsx @@ -62,20 +62,75 @@ describe('EntityContentBlueprint', () => { { "anyOf": [ { - "type": [ - "string", - "number", - "boolean", - ], + "additionalProperties": { + "anyOf": [ + { + "type": [ + "string", + "number", + "boolean", + ], + }, + { + "additionalProperties": false, + "properties": { + "$exists": { + "type": "boolean", + }, + }, + "required": [ + "$exists", + ], + "type": "object", + }, + { + "additionalProperties": false, + "properties": { + "$in": { + "items": { + "$ref": "#/properties/filter/anyOf/1/anyOf/0/anyOf/0/additionalProperties/anyOf/0", + }, + "type": "array", + }, + }, + "required": [ + "$in", + ], + "type": "object", + }, + { + "additionalProperties": false, + "properties": { + "$contains": { + "$ref": "#/properties/filter/anyOf/1", + }, + }, + "required": [ + "$contains", + ], + "type": "object", + }, + ], + }, + "propertyNames": { + "pattern": "^(?!\\$).*$", + }, + "type": "object", }, { - "items": { - "$ref": "#/properties/filter/anyOf/1/anyOf/0/anyOf/0", + "additionalProperties": { + "not": {}, }, - "type": "array", + "propertyNames": { + "pattern": "^\\$", + }, + "type": "object", }, ], }, + { + "$ref": "#/properties/filter/anyOf/1/anyOf/0/anyOf/0/additionalProperties/anyOf/0", + }, { "additionalProperties": false, "properties": { @@ -118,58 +173,6 @@ describe('EntityContentBlueprint', () => { ], "type": "object", }, - { - "additionalProperties": { - "anyOf": [ - { - "$ref": "#/properties/filter/anyOf/1/anyOf/0", - }, - { - "additionalProperties": false, - "properties": { - "$exists": { - "type": "boolean", - }, - }, - "required": [ - "$exists", - ], - "type": "object", - }, - { - "additionalProperties": false, - "properties": { - "$in": { - "items": { - "$ref": "#/properties/filter/anyOf/1/anyOf/0/anyOf/0", - }, - "type": "array", - }, - }, - "required": [ - "$in", - ], - "type": "object", - }, - { - "additionalProperties": false, - "properties": { - "$contains": { - "$ref": "#/properties/filter/anyOf/1", - }, - }, - "required": [ - "$contains", - ], - "type": "object", - }, - ], - }, - "propertyNames": { - "pattern": "^(?!\\$).*$", - }, - "type": "object", - }, ], }, ], diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityContextMenuItemBlueprint.test.tsx b/plugins/catalog-react/src/alpha/blueprints/EntityContextMenuItemBlueprint.test.tsx index 55598f5de1..d92fcc1863 100644 --- a/plugins/catalog-react/src/alpha/blueprints/EntityContextMenuItemBlueprint.test.tsx +++ b/plugins/catalog-react/src/alpha/blueprints/EntityContextMenuItemBlueprint.test.tsx @@ -73,20 +73,75 @@ describe('EntityContextMenuItemBlueprint', () => { { "anyOf": [ { - "type": [ - "string", - "number", - "boolean", - ], + "additionalProperties": { + "anyOf": [ + { + "type": [ + "string", + "number", + "boolean", + ], + }, + { + "additionalProperties": false, + "properties": { + "$exists": { + "type": "boolean", + }, + }, + "required": [ + "$exists", + ], + "type": "object", + }, + { + "additionalProperties": false, + "properties": { + "$in": { + "items": { + "$ref": "#/properties/filter/anyOf/0/anyOf/0/additionalProperties/anyOf/0", + }, + "type": "array", + }, + }, + "required": [ + "$in", + ], + "type": "object", + }, + { + "additionalProperties": false, + "properties": { + "$contains": { + "$ref": "#/properties/filter", + }, + }, + "required": [ + "$contains", + ], + "type": "object", + }, + ], + }, + "propertyNames": { + "pattern": "^(?!\\$).*$", + }, + "type": "object", }, { - "items": { - "$ref": "#/properties/filter/anyOf/0/anyOf/0", + "additionalProperties": { + "not": {}, }, - "type": "array", + "propertyNames": { + "pattern": "^\\$", + }, + "type": "object", }, ], }, + { + "$ref": "#/properties/filter/anyOf/0/anyOf/0/additionalProperties/anyOf/0", + }, { "additionalProperties": false, "properties": { @@ -129,58 +184,6 @@ describe('EntityContextMenuItemBlueprint', () => { ], "type": "object", }, - { - "additionalProperties": { - "anyOf": [ - { - "$ref": "#/properties/filter/anyOf/0", - }, - { - "additionalProperties": false, - "properties": { - "$exists": { - "type": "boolean", - }, - }, - "required": [ - "$exists", - ], - "type": "object", - }, - { - "additionalProperties": false, - "properties": { - "$in": { - "items": { - "$ref": "#/properties/filter/anyOf/0/anyOf/0", - }, - "type": "array", - }, - }, - "required": [ - "$in", - ], - "type": "object", - }, - { - "additionalProperties": false, - "properties": { - "$contains": { - "$ref": "#/properties/filter", - }, - }, - "required": [ - "$contains", - ], - "type": "object", - }, - ], - }, - "propertyNames": { - "pattern": "^(?!\\$).*$", - }, - "type": "object", - }, ], }, }, diff --git a/plugins/catalog-react/src/alpha/predicates/createEntityPredicateSchema.test.ts b/plugins/catalog-react/src/alpha/predicates/createEntityPredicateSchema.test.ts index 4fdf921c1a..3553fa932b 100644 --- a/plugins/catalog-react/src/alpha/predicates/createEntityPredicateSchema.test.ts +++ b/plugins/catalog-react/src/alpha/predicates/createEntityPredicateSchema.test.ts @@ -16,84 +16,100 @@ import { z } from 'zod'; import { createEntityPredicateSchema } from './createEntityPredicateSchema'; +import { EntityPredicate } from './types'; describe('createEntityPredicateSchema', () => { const schema = createEntityPredicateSchema(z); - it.each([ - 'string', - '', - [], - 1, - { kind: 'component', 'spec.type': 'service' }, - { 'metadata.tags': { $in: ['java'] } }, - { - $all: [ - { 'metadata.tags': { $contains: 'java' } }, - { 'metadata.tags': { $contains: 'spring' } }, - ], - }, - { 'metadata.tags': ['java', 'spring'] }, - { 'metadata.tags': { $in: ['go'] } }, - { 'metadata.tags.0': 'java' }, - { $not: { 'metadata.tags': { $in: ['java'] } } }, - { - $any: [{ kind: 'component', 'spec.type': 'service' }, { kind: 'group' }], - }, - { - relations: { - $contains: { type: 'ownedBy', targetRef: 'group:default/g' }, + describe('valid predicates', () => { + const predicates: EntityPredicate[] = [ + 'string', + '', + 1, + { kind: 'component', 'spec.type': 'service' }, + { 'metadata.tags': { $in: ['java'] } }, + { 'metadata.tags': { $contains: 'java' } }, + { + $all: [ + { 'metadata.tags': { $contains: 'java' } }, + { 'metadata.tags': { $contains: 'spring' } }, + ], }, - }, - { - metadata: { $contains: { name: 'a' } }, - }, - { kind: 'component', 'spec.type': { $in: ['service', 'website'] } }, - { - $any: [ - { - $all: [ - { - kind: 'component', - 'spec.type': { $in: ['service', 'website'] }, - }, - ], + { 'metadata.tags': { $in: ['go'] } }, + { 'metadata.tags.0': 'java' }, + { $not: { 'metadata.tags': { $in: ['java'] } } }, + { + $any: [ + { kind: 'component', 'spec.type': 'service' }, + { kind: 'group' }, + ], + }, + { + relations: { + $contains: { type: 'ownedBy', targetRef: 'group:default/g' }, }, - { $all: [{ kind: 'api', 'spec.type': 'grpc' }] }, - ], - }, - { kind: 'component', 'spec.type': { $in: ['service'] } }, - { 'spec.owner': { $exists: true } }, - { 'spec.owner': { $exists: false } }, - { 'spec.type': 'service' }, - { $not: { 'spec.type': 'service' } }, - { - kind: 'component', - 'metadata.annotations.github.com/repo': { $exists: true }, - }, - { $all: [{ x: { $exists: true } }] }, - { $any: [{ x: { $exists: true } }] }, - { $not: { x: { $exists: true } } }, - { $not: { $all: [{ x: { $exists: true } }] } }, - ])('should accept valid predicate %j', predicate => { - expect(schema.parse(predicate)).toEqual(predicate); + }, + { + metadata: { $contains: { name: 'a' } }, + }, + { kind: 'component', 'spec.type': { $in: ['service', 'website'] } }, + { + $any: [ + { + $all: [ + { + kind: 'component', + 'spec.type': { $in: ['service', 'website'] }, + }, + ], + }, + { $all: [{ kind: 'api', 'spec.type': 'grpc' }] }, + ], + }, + { kind: 'component', 'spec.type': { $in: ['service'] } }, + { 'spec.owner': { $exists: true } }, + { 'spec.owner': { $exists: false } }, + { 'spec.type': 'service' }, + { $not: { 'spec.type': 'service' } }, + { + kind: 'component', + 'metadata.annotations.github.com/repo': { $exists: true }, + }, + { $all: [{ x: { $exists: true } }] }, + { $any: [{ x: { $exists: true } }] }, + { $not: { x: { $exists: true } } }, + { $not: { $all: [{ x: { $exists: true } }] } }, + ]; + + it.each(predicates)('should accept valid predicate %j', predicate => { + expect(schema.parse(predicate)).toEqual(predicate); + }); }); - it.each([ - { kind: { 1: 'foo' } }, - { kind: { foo: 'bar' } }, - { kind: { $unknown: 'foo' } }, - { kind: { $in: 'foo' } }, - { kind: { $in: [{ x: 'foo' }] } }, - { kind: { $in: [{ x: 'foo' }] } }, - { 'spec.type': null }, - { $all: [{ x: { $unknown: true } }] }, - { $any: [{ x: { $unknown: true } }] }, - { $not: { x: { $unknown: true } } }, - { $not: { $all: [{ x: { $unknown: true } }] } }, - { $unknown: 'foo' }, - ])('should reject invalid predicate %j', predicate => { - const result = schema.safeParse(predicate); - expect(result.success).toBe(false); + describe('invalid predicates', () => { + const predicates: Array< + Exclude + > = [ + [], + ['foo', 'bar'], + { kind: { 1: 'foo' } }, + { kind: { foo: 'bar' } }, + { kind: { $unknown: 'foo' } }, + { kind: { $in: 'foo' } }, + { kind: { $in: [{ x: 'foo' }] } }, + { kind: { $in: [{ x: 'foo' }] } }, + { 'spec.type': null }, + { $all: [{ x: { $unknown: true } }] }, + { $any: [{ x: { $unknown: true } }] }, + { $not: { x: { $unknown: true } } }, + { $not: { $all: [{ x: { $unknown: true } }] } }, + { $unknown: 'foo' }, + { 'metadata.tags': ['foo', 'bar'] }, + ]; + + it.each(predicates)('should reject invalid predicate %j', predicate => { + const result = schema.safeParse(predicate); + expect(result.success).toBe(false); + }); }); }); diff --git a/plugins/catalog-react/src/alpha/predicates/createEntityPredicateSchema.ts b/plugins/catalog-react/src/alpha/predicates/createEntityPredicateSchema.ts index cfd2e451df..3e31ec4d35 100644 --- a/plugins/catalog-react/src/alpha/predicates/createEntityPredicateSchema.ts +++ b/plugins/catalog-react/src/alpha/predicates/createEntityPredicateSchema.ts @@ -14,33 +14,44 @@ * limitations under the License. */ -import { EntityPredicate, EntityPredicateValue } from './types'; +import { + EntityPredicate, + EntityPredicateExpression, + EntityPredicatePrimitive, + EntityPredicateValue, +} from './types'; import type { z as zImpl, ZodType } from 'zod'; /** @internal */ export function createEntityPredicateSchema(z: typeof zImpl) { - const primitiveSchema = z.union([z.string(), z.number(), z.boolean()]); - - const comparableValueSchema = z.union([ - primitiveSchema, - z.array(primitiveSchema), - ]); + const primitiveSchema = z.union([ + z.string(), + z.number(), + z.boolean(), + ]) as ZodType; // eslint-disable-next-line prefer-const let valuePredicateSchema: ZodType; + const expressionSchema = z.lazy(() => + z.union([ + z.record(z.string().regex(/^(?!\$).*$/), valuePredicateSchema), + z.record(z.string().regex(/^\$/), z.never()), + ]), + ) as ZodType; + const predicateSchema = z.lazy(() => z.union([ - comparableValueSchema, + expressionSchema, + primitiveSchema, z.object({ $all: z.array(predicateSchema) }), z.object({ $any: z.array(predicateSchema) }), z.object({ $not: predicateSchema }), - z.record(z.string().regex(/^(?!\$).*$/), valuePredicateSchema), ]), ) as ZodType; valuePredicateSchema = z.union([ - comparableValueSchema, + primitiveSchema, z.object({ $exists: z.boolean() }), z.object({ $in: z.array(primitiveSchema) }), z.object({ $contains: predicateSchema }), diff --git a/plugins/catalog-react/src/alpha/predicates/types.ts b/plugins/catalog-react/src/alpha/predicates/types.ts index a4a2e9199f..f19026c01b 100644 --- a/plugins/catalog-react/src/alpha/predicates/types.ts +++ b/plugins/catalog-react/src/alpha/predicates/types.ts @@ -34,7 +34,7 @@ export type EntityPredicateValue = | EntityPredicatePrimitive | { $exists: boolean } | { $in: EntityPredicatePrimitive[] } - | { $contains: EntityPredicateExpression }; + | { $contains: EntityPredicate }; /** @alpha */ export type EntityPredicatePrimitive = string | number | boolean; diff --git a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.test.tsx b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.test.tsx index b8c8e58d32..87c5c9a002 100644 --- a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.test.tsx +++ b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.test.tsx @@ -362,6 +362,76 @@ describe('', () => { owners: new EntityOwnerFilter(['group:default/team-b']), }); }); + + it('calls fetch with lowercased input and displays results', async () => { + const updateFilters = jest.fn(); + const someOwnerEntities: Entity[] = [ + { + apiVersion: '1', + kind: 'Group', + metadata: { + name: 'some-owner', + }, + }, + { + apiVersion: '1', + kind: 'Group', + metadata: { + name: 'some-owner-2', + }, + spec: { + profile: { + displayName: 'Some Owner 2', + }, + }, + }, + ]; + mockCatalogApi.queryEntities.mockImplementation(async _request => { + const totalItems = 2; + return { + items: someOwnerEntities, + pageInfo: { + nextCursor: '', + }, + totalItems, + }; + }); + await renderInTestApp( + + + + + , + ); + + expect(mockCatalogApi.getEntitiesByRefs).not.toHaveBeenCalled(); + expect(updateFilters).toHaveBeenLastCalledWith({ + owners: undefined, + }); + + const input = screen.getByRole('textbox'); + fireEvent.change(input, { target: { value: 'Some-Owner' } }); + + await waitFor(() => + expect(screen.getByText('some-owner')).toBeInTheDocument(), + ); + expect(mockCatalogApi.queryEntities).toHaveBeenLastCalledWith( + expect.objectContaining({ + fullTextFilter: expect.objectContaining({ + term: 'some-owner', + }), + }), + ); + + fireEvent.click(screen.getByText('some-owner')); + expect(updateFilters).toHaveBeenLastCalledWith({ + owners: new EntityOwnerFilter(['group:default/some-owner']), + }); + }); }); describe('', () => { diff --git a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx index 247a3784f4..ed200c77c4 100644 --- a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx +++ b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx @@ -146,7 +146,11 @@ export const EntityOwnerPicker = (props?: EntityOwnerPickerProps) => { mode, initialSelectedOwnersRefs: selectedOwners, }); - useDebouncedEffect(() => handleFetch({ text }), [text, handleFetch], 250); + useDebouncedEffect( + () => handleFetch({ text: text.toLocaleLowerCase('en-US') }), + [text, handleFetch], + 250, + ); const availableOwners = value?.items || []; diff --git a/plugins/catalog-react/src/components/UserListPicker/useAllEntitiesCount.test.tsx b/plugins/catalog-react/src/components/UserListPicker/useAllEntitiesCount.test.tsx index 0ba231b72a..07eaa479fd 100644 --- a/plugins/catalog-react/src/components/UserListPicker/useAllEntitiesCount.test.tsx +++ b/plugins/catalog-react/src/components/UserListPicker/useAllEntitiesCount.test.tsx @@ -68,15 +68,15 @@ describe('useAllEntitiesCount', () => { ), }); - await waitFor(() => + await waitFor(() => { expect(mockCatalogApi.queryEntities).toHaveBeenCalledWith({ filter: { 'relations.ownedBy': ['user:default/owner'], }, limit: 0, - }), - ); - expect(result.current).toEqual({ count: 10, loading: false }); + }); + expect(result.current).toEqual({ count: 10, loading: false }); + }); }); it(`shouldn't invoke the endpoint at startup, when filters are missing`, async () => { diff --git a/plugins/catalog-react/src/deprecated.tsx b/plugins/catalog-react/src/deprecated.tsx index 74186d9c29..7d347dda6b 100644 --- a/plugins/catalog-react/src/deprecated.tsx +++ b/plugins/catalog-react/src/deprecated.tsx @@ -15,10 +15,12 @@ */ import { PropsWithChildren, useCallback, useMemo, useState } from 'react'; +import { createVersionedValueMap } from '@backstage/version-bridge'; import { DefaultEntityFilters, - EntityListContext, EntityListContextProps, + NewEntityListContext, + OldEntityListContext, } from './hooks/useEntityListProvider'; /** @@ -81,8 +83,18 @@ export function MockEntityListContextProvider< ); return ( - + {children} - + ); } + +/** + * Creates new context for entity listing and filtering. + * + * @public + * @deprecated Please use `EntityListProvider` and `EntityListProvider` instead. + */ +export const EntityListContext = OldEntityListContext; diff --git a/plugins/catalog-react/src/hooks/index.ts b/plugins/catalog-react/src/hooks/index.ts index befcce520d..2bce5a4673 100644 --- a/plugins/catalog-react/src/hooks/index.ts +++ b/plugins/catalog-react/src/hooks/index.ts @@ -24,11 +24,7 @@ export type { EntityProviderProps, AsyncEntityProviderProps, } from './useEntity'; -export { - EntityListContext, - EntityListProvider, - useEntityList, -} from './useEntityListProvider'; +export { EntityListProvider, useEntityList } from './useEntityListProvider'; export type { DefaultEntityFilters, EntityListContextProps, diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx index f7090f330b..7b9d17d82e 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx @@ -42,7 +42,13 @@ import { } from '../filters'; import { createDeferred } from '@backstage/types'; import { EntityListPagination } from '../types'; -import { EntityListProvider, useEntityList } from './useEntityListProvider'; +import { + EntityListContextProps, + EntityListProvider, + NewEntityListContext, + useEntityList, +} from './useEntityListProvider'; +import { createVersionedValueMap } from '@backstage/version-bridge'; const entities: Entity[] = [ { @@ -1048,3 +1054,59 @@ describe(``, () => { ); }); }); + +describe('versioned context', () => { + it('should work explicitly with new versioned contexts', () => { + const value: EntityListContextProps = { + filters: {}, + entities: [], + backendEntities: [], + updateFilters: jest.fn(), + queryParameters: {}, + loading: true, + limit: 277, + setLimit: jest.fn(), + setOffset: jest.fn(), + paginationMode: 'none', + }; + + const { result } = renderHook(() => useEntityList(), { + wrapper: ({ children }) => { + const InitialFiltersWrapper = (f: PropsWithChildren<{}>) => { + const { updateFilters } = useEntityList(); + useMountEffect(() => { + updateFilters({ + kind: new EntityKindFilter('component', 'Component'), + }); + }); + return <>{f.children}; + }; + + return ( + + + + {children} + + + + ); + }, + }); + + expect(result.current.limit).toBe(277); + }); +}); diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx index 8c4927ad0a..a2ab4ea377 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx @@ -17,6 +17,11 @@ import { QueryEntitiesResponse } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; import { useApi } from '@backstage/core-plugin-api'; +import { + createVersionedContext, + createVersionedValueMap, + useVersionedContext, +} from '@backstage/version-bridge'; import { compact, isEqual } from 'lodash'; import qs from 'qs'; import { @@ -122,11 +127,17 @@ export type EntityListContextProps< paginationMode: PaginationMode; }; +// This context has support for multiple concurrent versions of this package. +// It is currently used in parallel with the old context in order to provide +// a smooth transition, but will eventually be the only context we use. +export const NewEntityListContext = createVersionedContext<{ + 1: EntityListContextProps; +}>('entity-list-context'); + /** * Creates new context for entity listing and filtering. - * @public */ -export const EntityListContext = createContext< +export const OldEntityListContext = createContext< EntityListContextProps | undefined >(undefined); @@ -487,9 +498,13 @@ export const EntityListProvider = ( ); return ( - - {props.children} - + + + {props.children} + + ); }; @@ -500,8 +515,22 @@ export const EntityListProvider = ( export function useEntityList< EntityFilters extends DefaultEntityFilters = DefaultEntityFilters, >(): EntityListContextProps { - const context = useContext(EntityListContext); - if (!context) - throw new Error('useEntityList must be used within EntityListProvider'); - return context; + const versionedHolder = useVersionedContext<{ + 1: EntityListContextProps; + }>('entity-list-context'); + const oldContext = useContext(OldEntityListContext); + + if (versionedHolder) { + const value = versionedHolder.atVersion(1); + if (!value) { + throw new Error('EntityListContext v1 not available'); + } + return value; + } + + if (oldContext) { + return oldContext; + } + + throw new Error('useEntityList must be used within EntityListProvider'); } diff --git a/plugins/catalog-react/src/testUtils/MockEntityListContextProvider.tsx b/plugins/catalog-react/src/testUtils/MockEntityListContextProvider.tsx index 0dcbbed8e8..e75e7e20e3 100644 --- a/plugins/catalog-react/src/testUtils/MockEntityListContextProvider.tsx +++ b/plugins/catalog-react/src/testUtils/MockEntityListContextProvider.tsx @@ -17,9 +17,10 @@ import { PropsWithChildren, useCallback, useMemo, useState } from 'react'; import { DefaultEntityFilters, - EntityListContext, EntityListContextProps, } from '@backstage/plugin-catalog-react'; +import { createVersionedValueMap } from '@backstage/version-bridge'; +import { NewEntityListContext } from '../hooks/useEntityListProvider'; /** * Simplifies testing of code that uses the entity list hooks. @@ -82,8 +83,10 @@ export function MockEntityListContextProvider< ); return ( - + {children} - + ); } diff --git a/plugins/catalog-unprocessed-entities-common/CHANGELOG.md b/plugins/catalog-unprocessed-entities-common/CHANGELOG.md index 962c3dff33..2a9cca8511 100644 --- a/plugins/catalog-unprocessed-entities-common/CHANGELOG.md +++ b/plugins/catalog-unprocessed-entities-common/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-catalog-unprocessed-entities-common +## 0.0.12 + +### Patch Changes + +- df4d646: Moved types, API and client to the common package, allowing both frontend and + backend plugins to use the `CatalogUnprocessedEntitiesClient`. + + The following types, clients and interfaces have been deprecated and should be + imported from the `@backstage/plugin-catalog-unprocessed-entities-common` instead: + `CatalogUnprocessedEntitiesApi`, `CatalogUnprocessedEntitiesApiResponse`, `UnprocessedEntity`, + `UnprocessedEntityCache`, `UnprocessedEntityError`, `CatalogUnprocessedEntitiesClient`. + + All those types, clients and interfaces are re-exported temporarily in the + `@backstage/plugin-catalog-unprocessed-entities` package until cleaned up. + +## 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. + ## 0.0.11 ### Patch Changes diff --git a/plugins/catalog-unprocessed-entities-common/package.json b/plugins/catalog-unprocessed-entities-common/package.json index 49ead8da1b..58423cad91 100644 --- a/plugins/catalog-unprocessed-entities-common/package.json +++ b/plugins/catalog-unprocessed-entities-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-unprocessed-entities-common", - "version": "0.0.11", + "version": "0.0.12", "description": "Common functionalities for the catalog-unprocessed-entities plugin", "backstage": { "role": "common-library", @@ -37,6 +37,8 @@ "test": "backstage-cli package test" }, "dependencies": { + "@backstage/catalog-model": "workspace:^", + "@backstage/errors": "workspace:^", "@backstage/plugin-permission-common": "workspace:^" }, "devDependencies": { diff --git a/plugins/catalog-unprocessed-entities-common/report.api.md b/plugins/catalog-unprocessed-entities-common/report.api.md index 03b316eebb..e373a3f45b 100644 --- a/plugins/catalog-unprocessed-entities-common/report.api.md +++ b/plugins/catalog-unprocessed-entities-common/report.api.md @@ -4,6 +4,53 @@ ```ts import { BasicPermission } from '@backstage/plugin-permission-common'; +import { Entity } from '@backstage/catalog-model'; + +// @public +export interface CatalogUnprocessedEntitiesApi { + delete( + entityId: string, + options?: UnprocessedEntitiesRequestOptions, + ): Promise; + failed( + options?: UnprocessedEntitiesRequestOptions, + ): Promise; + pending( + options?: UnprocessedEntitiesRequestOptions, + ): Promise; +} + +// @public +export type CatalogUnprocessedEntitiesApiResponse = { + entities: UnprocessedEntity[]; +}; + +// @public +export class CatalogUnprocessedEntitiesClient + implements CatalogUnprocessedEntitiesApi +{ + constructor( + discovery: { + getBaseUrl(pluginId: string): Promise; + }, + fetchApi?: { + fetch: typeof fetch; + }, + ); + // (undocumented) + delete( + entityId: string, + options?: UnprocessedEntitiesRequestOptions, + ): Promise; + // (undocumented) + failed( + options?: UnprocessedEntitiesRequestOptions, + ): Promise; + // (undocumented) + pending( + options?: UnprocessedEntitiesRequestOptions, + ): Promise; +} // @public export const unprocessedEntitiesDeletePermission: BasicPermission; @@ -12,4 +59,42 @@ export const unprocessedEntitiesDeletePermission: BasicPermission; export const unprocessedEntitiesPermissions: { unprocessedEntitiesDeletePermission: BasicPermission; }; + +// @public +export interface UnprocessedEntitiesRequestOptions { + // (undocumented) + token?: string; +} + +// @public +export type UnprocessedEntity = { + entity_id: string; + entity_ref: string; + unprocessed_entity: Entity; + unprocessed_hash?: string; + processed_entity?: Entity; + result_hash?: string; + cache?: UnprocessedEntityCache; + next_update_at: string | Date; + last_discovery_at: string | Date; + errors?: UnprocessedEntityError[]; + location_key?: string; +}; + +// @public +export type UnprocessedEntityCache = { + ttl: number; + cache: object; +}; + +// @public +export type UnprocessedEntityError = { + name: string; + message: string; + cause: { + name: string; + message: string; + stack: string; + }; +}; ``` diff --git a/plugins/catalog-unprocessed-entities-common/src/api/api.ts b/plugins/catalog-unprocessed-entities-common/src/api/api.ts new file mode 100644 index 0000000000..02b230aaec --- /dev/null +++ b/plugins/catalog-unprocessed-entities-common/src/api/api.ts @@ -0,0 +1,124 @@ +/* + * 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 { CatalogUnprocessedEntitiesApiResponse } from '../types'; +import { ResponseError } from '@backstage/errors'; + +/** + * Options you can pass into a catalog request for additional information. + * + * @public + */ +export interface UnprocessedEntitiesRequestOptions { + token?: string; +} + +/** + * Interface for the CatalogUnprocessedEntitiesApi. + * + * @public + */ +export interface CatalogUnprocessedEntitiesApi { + /** + * Returns a list of entities with state 'pending' + * + * @param options - Additional options + */ + pending( + options?: UnprocessedEntitiesRequestOptions, + ): Promise; + /** + * Returns a list of entities with state 'failed' + * + * @param options - Additional options + */ + failed( + options?: UnprocessedEntitiesRequestOptions, + ): Promise; + /** + * Deletes an entity from the refresh_state table + * + * @param entityId - The ID of the entity to delete + * @param options - Additional options + */ + delete( + entityId: string, + options?: UnprocessedEntitiesRequestOptions, + ): Promise; +} + +/** + * Default API implementation for the Catalog Unprocessed Entities plugin + * + * @public + */ +export class CatalogUnprocessedEntitiesClient + implements CatalogUnprocessedEntitiesApi +{ + private readonly discovery: { getBaseUrl(pluginId: string): Promise }; + private readonly fetchApi: { fetch: typeof fetch }; + + constructor( + discovery: { getBaseUrl(pluginId: string): Promise }, + fetchApi?: { fetch: typeof fetch }, + ) { + this.discovery = discovery; + this.fetchApi = fetchApi ?? { fetch }; + } + + private async fetch( + method: string, + path: string, + options?: UnprocessedEntitiesRequestOptions, + ): Promise { + const url = await this.discovery.getBaseUrl('catalog'); + const resp = await this.fetchApi.fetch(`${url}/${path}`, { + method, + headers: { + ...(options?.token ? { Authorization: `Bearer ${options.token}` } : {}), + }, + }); + + if (!resp.ok) { + throw await ResponseError.fromResponse(resp); + } + + return resp.status === 204 ? (resp as T) : await resp.json(); + } + + async pending( + options?: UnprocessedEntitiesRequestOptions, + ): Promise { + return await this.fetch('GET', 'entities/unprocessed/pending', options); + } + + async failed( + options?: UnprocessedEntitiesRequestOptions, + ): Promise { + return await this.fetch('GET', 'entities/unprocessed/failed', options); + } + + async delete( + entityId: string, + options?: UnprocessedEntitiesRequestOptions, + ): Promise { + await this.fetch( + 'DELETE', + `entities/unprocessed/delete/${entityId}`, + options, + ); + } +} diff --git a/packages/core-plugin-api/src/apis/alpha.ts b/plugins/catalog-unprocessed-entities-common/src/api/index.ts similarity index 88% rename from packages/core-plugin-api/src/apis/alpha.ts rename to plugins/catalog-unprocessed-entities-common/src/api/index.ts index b22d3df574..5b8bf17e1d 100644 --- a/packages/core-plugin-api/src/apis/alpha.ts +++ b/plugins/catalog-unprocessed-entities-common/src/api/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * 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. @@ -13,4 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './definitions/alpha'; +export * from './api'; diff --git a/plugins/catalog-unprocessed-entities-common/src/index.ts b/plugins/catalog-unprocessed-entities-common/src/index.ts index cd5a39c731..d11cb4fdb5 100644 --- a/plugins/catalog-unprocessed-entities-common/src/index.ts +++ b/plugins/catalog-unprocessed-entities-common/src/index.ts @@ -20,4 +20,6 @@ * @packageDocumentation */ +export * from './api'; export * from './permissions'; +export * from './types'; diff --git a/plugins/catalog-unprocessed-entities-common/src/types.ts b/plugins/catalog-unprocessed-entities-common/src/types.ts new file mode 100644 index 0000000000..d7af485e83 --- /dev/null +++ b/plugins/catalog-unprocessed-entities-common/src/types.ts @@ -0,0 +1,66 @@ +/* + * 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 { Entity } from '@backstage/catalog-model'; + +/** + * Unprocessed entity data stored in the database. + * @public + */ +export type UnprocessedEntity = { + entity_id: string; + entity_ref: string; + unprocessed_entity: Entity; + unprocessed_hash?: string; + processed_entity?: Entity; + result_hash?: string; + cache?: UnprocessedEntityCache; + next_update_at: string | Date; + last_discovery_at: string | Date; // remove? + errors?: UnprocessedEntityError[]; + location_key?: string; +}; + +/** + * Unprocessed entity cache stored in the database. + * @public + */ +export type UnprocessedEntityCache = { + ttl: number; + cache: object; +}; + +/** + * Unprocessed entity error information stored in the database. + * @public + */ +export type UnprocessedEntityError = { + name: string; + message: string; + cause: { + name: string; + message: string; + stack: string; + }; +}; + +/** + * Response expected by the {@link CatalogUnprocessedEntitiesApi} + * + * @public + */ +export type CatalogUnprocessedEntitiesApiResponse = { + entities: UnprocessedEntity[]; +}; diff --git a/plugins/catalog-unprocessed-entities/CHANGELOG.md b/plugins/catalog-unprocessed-entities/CHANGELOG.md index 4250130363..47be6beb8b 100644 --- a/plugins/catalog-unprocessed-entities/CHANGELOG.md +++ b/plugins/catalog-unprocessed-entities/CHANGELOG.md @@ -1,5 +1,58 @@ # @backstage/plugin-catalog-unprocessed-entities +## 0.2.24 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- df4d646: Moved types, API and client to the common package, allowing both frontend and + backend plugins to use the `CatalogUnprocessedEntitiesClient`. + + The following types, clients and interfaces have been deprecated and should be + imported from the `@backstage/plugin-catalog-unprocessed-entities-common` instead: + `CatalogUnprocessedEntitiesApi`, `CatalogUnprocessedEntitiesApiResponse`, `UnprocessedEntity`, + `UnprocessedEntityCache`, `UnprocessedEntityError`, `CatalogUnprocessedEntitiesClient`. + + All those types, clients and interfaces are re-exported temporarily in the + `@backstage/plugin-catalog-unprocessed-entities` package until cleaned up. + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.12 + +## 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 + +## 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 + ## 0.2.23 ### Patch Changes diff --git a/plugins/catalog-unprocessed-entities/package.json b/plugins/catalog-unprocessed-entities/package.json index 5610db4c50..016018b93f 100644 --- a/plugins/catalog-unprocessed-entities/package.json +++ b/plugins/catalog-unprocessed-entities/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-unprocessed-entities", - "version": "0.2.23", + "version": "0.2.24", "backstage": { "role": "frontend-plugin", "pluginId": "catalog-unprocessed-entities", @@ -50,12 +50,11 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/catalog-model": "workspace:^", - "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", + "@backstage/plugin-catalog-unprocessed-entities-common": "workspace:^", "@material-ui/core": "^4.9.13", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "^4.0.0-alpha.60", diff --git a/plugins/catalog-unprocessed-entities/report-alpha.api.md b/plugins/catalog-unprocessed-entities/report-alpha.api.md index 5518f6d273..50bc0c22fa 100644 --- a/plugins/catalog-unprocessed-entities/report-alpha.api.md +++ b/plugins/catalog-unprocessed-entities/report-alpha.api.md @@ -8,11 +8,12 @@ import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ApiFactory } from '@backstage/frontend-plugin-api'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; -import { IconComponent } from '@backstage/core-plugin-api'; +import { IconComponent } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; -import { RouteRef } from '@backstage/frontend-plugin-api'; +import { RouteRef } from '@backstage/core-plugin-api'; +import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) const _default: OverridableFrontendPlugin< @@ -45,7 +46,7 @@ const _default: OverridableFrontendPlugin< { title: string; icon: IconComponent; - routeRef: RouteRef; + routeRef: RouteRef_2; }, 'core.nav-item.target', {} @@ -54,7 +55,7 @@ const _default: OverridableFrontendPlugin< params: { title: string; icon: IconComponent; - routeRef: RouteRef; + routeRef: RouteRef_2; }; }>; 'page:catalog-unprocessed-entities': OverridableExtensionDefinition<{ @@ -70,7 +71,7 @@ const _default: OverridableFrontendPlugin< | ExtensionDataRef | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; @@ -81,7 +82,7 @@ const _default: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; loader: () => Promise; - routeRef?: RouteRef; + routeRef?: RouteRef_2; }; }>; } diff --git a/plugins/catalog-unprocessed-entities/report.api.md b/plugins/catalog-unprocessed-entities/report.api.md index adaf7af643..deeafb8a7c 100644 --- a/plugins/catalog-unprocessed-entities/report.api.md +++ b/plugins/catalog-unprocessed-entities/report.api.md @@ -3,26 +3,26 @@ > 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 { ApiRef } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; -import { Entity } from '@backstage/catalog-model'; +import { CatalogUnprocessedEntitiesApi as CatalogUnprocessedEntitiesApi_2 } from '@backstage/plugin-catalog-unprocessed-entities-common'; +import { CatalogUnprocessedEntitiesApiResponse as CatalogUnprocessedEntitiesApiResponse_2 } from '@backstage/plugin-catalog-unprocessed-entities-common'; import { JSX as JSX_2 } from 'react/jsx-runtime'; import { RouteRef } from '@backstage/core-plugin-api'; +import type { UnprocessedEntity as UnprocessedEntity_2 } from '@backstage/plugin-catalog-unprocessed-entities-common'; +import type { UnprocessedEntityCache as UnprocessedEntityCache_2 } from '@backstage/plugin-catalog-unprocessed-entities-common'; +import type { UnprocessedEntityError as UnprocessedEntityError_2 } from '@backstage/plugin-catalog-unprocessed-entities-common'; -// @public -export interface CatalogUnprocessedEntitiesApi { - delete(entityId: string): Promise; - failed(): Promise; - pending(): Promise; -} +// @public @deprecated +export interface CatalogUnprocessedEntitiesApi + extends CatalogUnprocessedEntitiesApi_2 {} // @public export const catalogUnprocessedEntitiesApiRef: ApiRef; -// @public -export type CatalogUnprocessedEntitiesApiResponse = { - entities: UnprocessedEntity[]; -}; +// @public @deprecated +export type CatalogUnprocessedEntitiesApiResponse = + CatalogUnprocessedEntitiesApiResponse_2; // @public export const CatalogUnprocessedEntitiesPage: () => JSX_2.Element; @@ -38,37 +38,14 @@ export const catalogUnprocessedEntitiesPlugin: BackstagePlugin< // @public (undocumented) export const UnprocessedEntitiesContent: () => JSX_2.Element; -// @public -export type UnprocessedEntity = { - entity_id: string; - entity_ref: string; - unprocessed_entity: Entity; - unprocessed_hash?: string; - processed_entity?: Entity; - result_hash?: string; - cache?: UnprocessedEntityCache; - next_update_at: string | Date; - last_discovery_at: string | Date; - errors?: UnprocessedEntityError[]; - location_key?: string; -}; +// @public @deprecated +export type UnprocessedEntity = UnprocessedEntity_2; -// @public -export type UnprocessedEntityCache = { - ttl: number; - cache: object; -}; +// @public @deprecated +export type UnprocessedEntityCache = UnprocessedEntityCache_2; -// @public -export type UnprocessedEntityError = { - name: string; - message: string; - cause: { - name: string; - message: string; - stack: string; - }; -}; +// @public @deprecated +export type UnprocessedEntityError = UnprocessedEntityError_2; // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx b/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx index 39a51184d3..5ccae907aa 100644 --- a/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx +++ b/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx @@ -27,10 +27,6 @@ import { catalogUnprocessedEntitiesApiRef, CatalogUnprocessedEntitiesClient, } from '../api'; -import { - compatWrapper, - convertLegacyRouteRef, -} from '@backstage/core-compat-api'; import QueueIcon from '@material-ui/icons/Queue'; import { rootRouteRef } from '../routes'; @@ -52,11 +48,11 @@ export const catalogUnprocessedEntitiesApi = ApiBlueprint.make({ export const catalogUnprocessedEntitiesPage = PageBlueprint.make({ params: { path: '/catalog-unprocessed-entities', - routeRef: convertLegacyRouteRef(rootRouteRef), + routeRef: rootRouteRef, loader: () => - import('../components/UnprocessedEntities').then(m => - compatWrapper(), - ), + import('../components/UnprocessedEntities').then(m => ( + + )), }, }); @@ -64,7 +60,7 @@ export const catalogUnprocessedEntitiesPage = PageBlueprint.make({ export const catalogUnprocessedEntitiesNavItem = NavItemBlueprint.make({ params: { title: 'Unprocessed Entities', - routeRef: convertLegacyRouteRef(rootRouteRef), + routeRef: rootRouteRef, icon: QueueIcon, }, }); @@ -74,7 +70,7 @@ export default createFrontendPlugin({ pluginId: 'catalog-unprocessed-entities', info: { packageJson: () => import('../../package.json') }, routes: { - root: convertLegacyRouteRef(rootRouteRef), + root: rootRouteRef, }, extensions: [ catalogUnprocessedEntitiesApi, diff --git a/plugins/catalog-unprocessed-entities/src/api/index.ts b/plugins/catalog-unprocessed-entities/src/api/index.ts index 67daa81319..cec9bd29bd 100644 --- a/plugins/catalog-unprocessed-entities/src/api/index.ts +++ b/plugins/catalog-unprocessed-entities/src/api/index.ts @@ -13,13 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { createApiRef } from '@backstage/core-plugin-api'; import { - DiscoveryApi, - createApiRef, - FetchApi, -} from '@backstage/core-plugin-api'; -import { ResponseError } from '@backstage/errors'; -import { UnprocessedEntity } from '../types'; + CatalogUnprocessedEntitiesApiResponse as CommonCatalogUnprocessedEntitiesApiResponse, + CatalogUnprocessedEntitiesApi as CommonCatalogUnprocessedEntitiesApi, + CatalogUnprocessedEntitiesClient as CommonCatalogUnprocessedEntitiesClient, +} from '@backstage/plugin-catalog-unprocessed-entities-common'; /** * {@link @backstage/core-plugin-api#ApiRef} for the {@link CatalogUnprocessedEntitiesApi} @@ -35,69 +34,24 @@ export const catalogUnprocessedEntitiesApiRef = * Response expected by the {@link CatalogUnprocessedEntitiesApi} * * @public + * @deprecated Use the type imported from `@backstage/plugin-catalog-unprocessed-entities-common` instead. */ -export type CatalogUnprocessedEntitiesApiResponse = { - entities: UnprocessedEntity[]; -}; +export type CatalogUnprocessedEntitiesApiResponse = + CommonCatalogUnprocessedEntitiesApiResponse; /** * Interface for the CatalogUnprocessedEntitiesApi. * * @public + * @deprecated Use the type imported from `@backstage/plugin-catalog-unprocessed-entities-common` instead. */ -export interface CatalogUnprocessedEntitiesApi { - /** - * Returns a list of entities with state 'pending' - */ - pending(): Promise; - /** - * Returns a list of entities with state 'failed' - */ - failed(): Promise; - /** - * Deletes an entity from the refresh_state table - */ - delete(entityId: string): Promise; -} +export interface CatalogUnprocessedEntitiesApi + extends CommonCatalogUnprocessedEntitiesApi {} /** * Default API implementation for the Catalog Unprocessed Entities plugin * * @public + * @deprecated Use the client imported from `@backstage/plugin-catalog-unprocessed-entities-common` instead. */ -export class CatalogUnprocessedEntitiesClient - implements CatalogUnprocessedEntitiesApi -{ - public discovery: DiscoveryApi; - public fetchApi: FetchApi; - - constructor(discovery: DiscoveryApi, fetchApi: FetchApi) { - this.discovery = discovery; - this.fetchApi = fetchApi; - } - - private async fetch(path: string, init?: RequestInit): Promise { - const url = await this.discovery.getBaseUrl('catalog'); - const resp = await this.fetchApi.fetch(`${url}/${path}`, init); - - if (!resp.ok) { - throw await ResponseError.fromResponse(resp); - } - - return resp.status === 204 ? (resp as T) : await resp.json(); - } - - async pending(): Promise { - return await this.fetch('entities/unprocessed/pending'); - } - - async failed(): Promise { - return await this.fetch('entities/unprocessed/failed'); - } - - async delete(entityId: string): Promise { - await this.fetch(`entities/unprocessed/delete/${entityId}`, { - method: 'DELETE', - }); - } -} +export class CatalogUnprocessedEntitiesClient extends CommonCatalogUnprocessedEntitiesClient {} diff --git a/plugins/catalog-unprocessed-entities/src/types.ts b/plugins/catalog-unprocessed-entities/src/types.ts index 65f21970f5..38b4b9dad7 100644 --- a/plugins/catalog-unprocessed-entities/src/types.ts +++ b/plugins/catalog-unprocessed-entities/src/types.ts @@ -13,45 +13,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; +import type { + UnprocessedEntity as CommonUnprocessedEntity, + UnprocessedEntityCache as CommonUnprocessedEntityCache, + UnprocessedEntityError as CommonUnprocessedEntityError, +} from '@backstage/plugin-catalog-unprocessed-entities-common'; /** * Unprocessed entity data stored in the database. * @public + * @deprecated Use the type imported from `@backstage/plugin-catalog-unprocessed-entities-common` instead. */ -export type UnprocessedEntity = { - entity_id: string; - entity_ref: string; - unprocessed_entity: Entity; - unprocessed_hash?: string; - processed_entity?: Entity; - result_hash?: string; - cache?: UnprocessedEntityCache; - next_update_at: string | Date; - last_discovery_at: string | Date; // remove? - errors?: UnprocessedEntityError[]; - location_key?: string; -}; +export type UnprocessedEntity = CommonUnprocessedEntity; /** * Unprocessed entity cache stored in the database. * @public + * @deprecated Use the type imported from `@backstage/plugin-catalog-unprocessed-entities-common` instead. */ -export type UnprocessedEntityCache = { - ttl: number; - cache: object; -}; +export type UnprocessedEntityCache = CommonUnprocessedEntityCache; /** * Unprocessed entity error information stored in the database. * @public + * @deprecated Use the type imported from `@backstage/plugin-catalog-unprocessed-entities-common` instead. */ -export type UnprocessedEntityError = { - name: string; - message: string; - cause: { - name: string; - message: string; - stack: string; - }; -}; +export type UnprocessedEntityError = CommonUnprocessedEntityError; diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 0982c99646..89f9bcdefe 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,75 @@ # @backstage/plugin-catalog +## 1.32.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- 91f5ed8: Fixed `catalogAboutEntityCard` to filter icon links before calling useProps(), preventing side effects from hooks in filtered-out links +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-search-react@1.10.1 + - @backstage/core-plugin-api@1.12.1 + - @backstage/core-compat-api@0.5.5 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-permission-react@0.4.39 + - @backstage/plugin-scaffolder-common@1.7.4 + - @backstage/plugin-techdocs-react@1.3.6 + +## 1.32.1-next.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- 91f5ed8: Fixed `catalogAboutEntityCard` to filter icon links before calling useProps(), preventing side effects from hooks in filtered-out links +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-compat-api@0.5.5-next.0 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-search-react@1.10.1-next.0 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + +## 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 + ## 1.32.0 ### Minor Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index d9a9726900..f2e4e1f610 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "1.32.0", + "version": "1.32.1", "description": "The Backstage plugin for browsing the Backstage catalog", "backstage": { "role": "frontend-plugin", diff --git a/plugins/catalog/report-alpha.api.md b/plugins/catalog/report-alpha.api.md index 6f63f50b0b..f70572e371 100644 --- a/plugins/catalog/report-alpha.api.md +++ b/plugins/catalog/report-alpha.api.md @@ -16,17 +16,18 @@ import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; -import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; -import { IconComponent } from '@backstage/core-plugin-api'; +import { ExternalRouteRef } from '@backstage/core-plugin-api'; +import { IconComponent } from '@backstage/frontend-plugin-api'; import { IconLinkVerticalProps } from '@backstage/core-components'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; -import { RouteRef } from '@backstage/frontend-plugin-api'; +import { RouteRef } from '@backstage/core-plugin-api'; +import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; import { SearchResultItemExtensionComponent } from '@backstage/plugin-search-react/alpha'; import { SearchResultItemExtensionPredicate } from '@backstage/plugin-search-react/alpha'; import { SearchResultListItemBlueprintParams } from '@backstage/plugin-search-react/alpha'; -import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) export const catalogTranslationRef: TranslationRef< @@ -124,17 +125,23 @@ const _default: OverridableFrontendPlugin< }>; }, { - viewTechDoc: ExternalRouteRef<{ - name: string; - kind: string; - namespace: string; - }>; - createComponent: ExternalRouteRef; - createFromTemplate: ExternalRouteRef<{ - namespace: string; - templateName: string; - }>; - unregisterRedirect: ExternalRouteRef; + viewTechDoc: ExternalRouteRef< + { + name: string; + kind: string; + namespace: string; + }, + true + >; + createComponent: ExternalRouteRef; + createFromTemplate: ExternalRouteRef< + { + namespace: string; + templateName: string; + }, + true + >; + unregisterRedirect: ExternalRouteRef; }, { 'api:catalog': OverridableExtensionDefinition<{ @@ -190,15 +197,7 @@ const _default: OverridableFrontendPlugin< initialFilter?: string | undefined; }; output: ExtensionDataRef; - inputs: { - [x: string]: ExtensionInput< - ExtensionDataRef, - { - singleton: boolean; - optional: boolean; - } - >; - }; + inputs: {}; kind: 'catalog-filter'; name: 'kind'; params: { @@ -224,15 +223,7 @@ const _default: OverridableFrontendPlugin< initialFilter?: 'all' | 'owned' | 'starred' | undefined; }; output: ExtensionDataRef; - inputs: { - [x: string]: ExtensionInput< - ExtensionDataRef, - { - singleton: boolean; - optional: boolean; - } - >; - }; + inputs: {}; kind: 'catalog-filter'; name: 'list'; params: { @@ -247,15 +238,7 @@ const _default: OverridableFrontendPlugin< mode?: 'all' | 'owners-only' | undefined; }; output: ExtensionDataRef; - inputs: { - [x: string]: ExtensionInput< - ExtensionDataRef, - { - singleton: boolean; - optional: boolean; - } - >; - }; + inputs: {}; kind: 'catalog-filter'; name: 'mode'; params: { @@ -759,7 +742,7 @@ const _default: OverridableFrontendPlugin< | ExtensionDataRef | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; @@ -852,7 +835,7 @@ const _default: OverridableFrontendPlugin< defaultGroup?: [Error: `Use the 'group' param instead`]; group?: keyof defaultEntityContentGroups | (string & {}); loader: () => Promise; - routeRef?: RouteRef; + routeRef?: RouteRef_2; filter?: string | EntityPredicate | ((entity: Entity) => boolean); }; }>; @@ -967,7 +950,7 @@ const _default: OverridableFrontendPlugin< { title: string; icon: IconComponent; - routeRef: RouteRef; + routeRef: RouteRef_2; }, 'core.nav-item.target', {} @@ -976,7 +959,7 @@ const _default: OverridableFrontendPlugin< params: { title: string; icon: IconComponent; - routeRef: RouteRef; + routeRef: RouteRef_2; }; }>; 'page:catalog': OverridableExtensionDefinition<{ @@ -988,7 +971,6 @@ const _default: OverridableFrontendPlugin< offset?: number | undefined; limit?: number | undefined; }; - } & { path: string | undefined; }; configInput: { @@ -1000,14 +982,13 @@ const _default: OverridableFrontendPlugin< limit?: number | undefined; } | undefined; - } & { path?: string | undefined; }; output: | ExtensionDataRef | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; @@ -1028,7 +1009,7 @@ const _default: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; loader: () => Promise; - routeRef?: RouteRef; + routeRef?: RouteRef_2; }; }>; 'page:catalog/entity': OverridableExtensionDefinition<{ @@ -1041,7 +1022,6 @@ const _default: OverridableFrontendPlugin< } >[] | undefined; - } & { path: string | undefined; }; configInput: { @@ -1053,14 +1033,13 @@ const _default: OverridableFrontendPlugin< } >[] | undefined; - } & { path?: string | undefined; }; output: | ExtensionDataRef | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; @@ -1091,7 +1070,7 @@ const _default: OverridableFrontendPlugin< | ConfigurableExtensionDataRef | ConfigurableExtensionDataRef | ConfigurableExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; @@ -1149,7 +1128,7 @@ const _default: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; loader: () => Promise; - routeRef?: RouteRef; + routeRef?: RouteRef_2; }; }>; 'search-result-list-item:catalog': OverridableExtensionDefinition<{ diff --git a/plugins/catalog/src/alpha/contextMenuItems.tsx b/plugins/catalog/src/alpha/contextMenuItems.tsx index 4173a4d074..4b37321c6f 100644 --- a/plugins/catalog/src/alpha/contextMenuItems.tsx +++ b/plugins/catalog/src/alpha/contextMenuItems.tsx @@ -37,7 +37,6 @@ import { import { rootRouteRef, unregisterRedirectRouteRef } from '../routes'; import { catalogEntityDeletePermission } from '@backstage/plugin-catalog-common/alpha'; import { useEffect } from 'react'; -import { compatWrapper } from '@backstage/core-compat-api'; export const copyEntityUrlContextMenuItem = EntityContextMenuItemBlueprint.make( { @@ -111,23 +110,21 @@ export const unregisterEntityContextMenuItem = title: t('entityContextMenu.unregisterMenuTitle'), disabled: !unregisterPermission.allowed, onClick: async () => { - dialogApi.showModal(({ dialog }: { dialog: DialogApiDialog }) => - compatWrapper( - dialog.close()} - onConfirm={() => { - dialog.close(); - navigate( - unregisterRedirectRoute - ? unregisterRedirectRoute() - : catalogRoute(), - ); - }} - />, - ), - ); + dialogApi.showModal(({ dialog }: { dialog: DialogApiDialog }) => ( + dialog.close()} + onConfirm={() => { + dialog.close(); + navigate( + unregisterRedirectRoute + ? unregisterRedirectRoute() + : catalogRoute(), + ); + }} + /> + )); }, }; }, diff --git a/plugins/catalog/src/alpha/entityCards.tsx b/plugins/catalog/src/alpha/entityCards.tsx index f99c5961a1..556a130dff 100644 --- a/plugins/catalog/src/alpha/entityCards.tsx +++ b/plugins/catalog/src/alpha/entityCards.tsx @@ -18,7 +18,6 @@ import { EntityIconLinkBlueprint, EntityCardBlueprint, } from '@backstage/plugin-catalog-react/alpha'; -import { compatWrapper } from '@backstage/core-compat-api'; import { createExtensionInput } from '@backstage/frontend-plugin-api'; import { HeaderIconLinkRow, @@ -42,12 +41,14 @@ export const catalogAboutEntityCard = EntityCardBlueprint.makeWithOverrides({ // The "useProps" functions may be calling other hooks, so we need to // call them in a component function to avoid breaking the rules of hooks. const links = inputs.iconLinks.reduce((rest, iconLink) => { - const props = iconLink.get(EntityIconLinkBlueprint.dataRefs.useProps)(); const filter = buildFilterFn( iconLink.get(EntityIconLinkBlueprint.dataRefs.filterFunction), iconLink.get(EntityIconLinkBlueprint.dataRefs.filterExpression), ); if (filter(entity)) { + const props = iconLink.get( + EntityIconLinkBlueprint.dataRefs.useProps, + )(); return [...rest, props]; } return rest; @@ -65,8 +66,8 @@ export const catalogAboutEntityCard = EntityCardBlueprint.makeWithOverrides({ const { InternalAboutCard } = await import( '../components/AboutCard/AboutCard' ); - return compatWrapper( - } />, + return ( + } /> ); }, }); @@ -79,9 +80,9 @@ export const catalogLinksEntityCard = EntityCardBlueprint.make({ type: 'info', filter: { 'metadata.links': { $exists: true } }, loader: async () => - import('../components/EntityLinksCard').then(m => - compatWrapper(), - ), + import('../components/EntityLinksCard').then(m => ( + + )), }, }); @@ -91,9 +92,9 @@ export const catalogLabelsEntityCard = EntityCardBlueprint.make({ type: 'info', filter: { 'metadata.labels': { $exists: true } }, loader: async () => - import('../components/EntityLabelsCard').then(m => - compatWrapper(), - ), + import('../components/EntityLabelsCard').then(m => ( + + )), }, }); @@ -102,9 +103,9 @@ export const catalogDependsOnComponentsEntityCard = EntityCardBlueprint.make({ params: { filter: { kind: 'component' }, loader: async () => - import('../components/DependsOnComponentsCard').then(m => - compatWrapper(), - ), + import('../components/DependsOnComponentsCard').then(m => ( + + )), }, }); @@ -113,9 +114,9 @@ export const catalogDependsOnResourcesEntityCard = EntityCardBlueprint.make({ params: { filter: { kind: 'component' }, loader: async () => - import('../components/DependsOnResourcesCard').then(m => - compatWrapper(), - ), + import('../components/DependsOnResourcesCard').then(m => ( + + )), }, }); @@ -124,9 +125,9 @@ export const catalogHasComponentsEntityCard = EntityCardBlueprint.make({ params: { filter: { kind: 'system' }, loader: async () => - import('../components/HasComponentsCard').then(m => - compatWrapper(), - ), + import('../components/HasComponentsCard').then(m => ( + + )), }, }); @@ -135,9 +136,9 @@ export const catalogHasResourcesEntityCard = EntityCardBlueprint.make({ params: { filter: { kind: 'system' }, loader: async () => - import('../components/HasResourcesCard').then(m => - compatWrapper(), - ), + import('../components/HasResourcesCard').then(m => ( + + )), }, }); @@ -146,9 +147,9 @@ export const catalogHasSubcomponentsEntityCard = EntityCardBlueprint.make({ params: { filter: { kind: 'component' }, loader: async () => - import('../components/HasSubcomponentsCard').then(m => - compatWrapper(), - ), + import('../components/HasSubcomponentsCard').then(m => ( + + )), }, }); @@ -157,9 +158,9 @@ export const catalogHasSubdomainsEntityCard = EntityCardBlueprint.make({ params: { filter: { kind: 'domain' }, loader: async () => - import('../components/HasSubdomainsCard').then(m => - compatWrapper(), - ), + import('../components/HasSubdomainsCard').then(m => ( + + )), }, }); @@ -168,9 +169,9 @@ export const catalogHasSystemsEntityCard = EntityCardBlueprint.make({ params: { filter: { kind: 'domain' }, loader: async () => - import('../components/HasSystemsCard').then(m => - compatWrapper(), - ), + import('../components/HasSystemsCard').then(m => ( + + )), }, }); diff --git a/plugins/catalog/src/alpha/navItems.tsx b/plugins/catalog/src/alpha/navItems.tsx index 0eb4e6827e..e72c20ebf4 100644 --- a/plugins/catalog/src/alpha/navItems.tsx +++ b/plugins/catalog/src/alpha/navItems.tsx @@ -15,13 +15,12 @@ */ import HomeIcon from '@material-ui/icons/Home'; -import { convertLegacyRouteRef } from '@backstage/core-compat-api'; import { NavItemBlueprint } from '@backstage/frontend-plugin-api'; import { rootRouteRef } from '../routes'; export const catalogNavItem = NavItemBlueprint.make({ params: { - routeRef: convertLegacyRouteRef(rootRouteRef), + routeRef: rootRouteRef, title: 'Catalog', icon: HomeIcon, }, diff --git a/plugins/catalog/src/alpha/pages.test.tsx b/plugins/catalog/src/alpha/pages.test.tsx index cc83b9168b..0b35e63979 100644 --- a/plugins/catalog/src/alpha/pages.test.tsx +++ b/plugins/catalog/src/alpha/pages.test.tsx @@ -721,18 +721,27 @@ describe('Entity page', () => { ); const { disabled } = params.useProps(); - await waitFor(async () => { - await userEvent.click(screen.getByTestId('menu-button')); - expect(screen.getByText('Test Title')).toBeInTheDocument(); - expect(screen.getByText('Test Icon')).toBeInTheDocument(); - const listItem = screen.getByText('Test Title').closest('li'); - expect(listItem).toHaveAttribute('aria-disabled', disabled.toString()); - if (!disabled) { - await userEvent.click(screen.getByText('Test Title')); - } - expect(onClickMock).toHaveBeenCalledTimes(disabled ? 0 : 1); - }); + // Wait for entity to load first + await waitFor(() => + expect(screen.getByText(/artist-lookup/)).toBeInTheDocument(), + ); + + await userEvent.click(screen.getByTestId('menu-button')); + + // Wait for menu to open + await waitFor(() => + expect(screen.getByText('Test Title')).toBeInTheDocument(), + ); + + expect(screen.getByText('Test Icon')).toBeInTheDocument(); + const listItem = screen.getByText('Test Title').closest('li'); + expect(listItem).toHaveAttribute('aria-disabled', disabled.toString()); + if (!disabled) { + await userEvent.click(screen.getByText('Test Title')); + } + + expect(onClickMock).toHaveBeenCalledTimes(disabled ? 0 : 1); }); it.each([ diff --git a/plugins/catalog/src/alpha/pages.tsx b/plugins/catalog/src/alpha/pages.tsx index 9d4e3b4bac..b9b8f9ba1f 100644 --- a/plugins/catalog/src/alpha/pages.tsx +++ b/plugins/catalog/src/alpha/pages.tsx @@ -14,10 +14,7 @@ * limitations under the License. */ -import { - compatWrapper, - convertLegacyRouteRef, -} from '@backstage/core-compat-api'; +import { convertLegacyRouteRef } from '@backstage/core-compat-api'; import { coreExtensionData, createExtensionInput, @@ -59,17 +56,17 @@ export const catalogPage = PageBlueprint.makeWithOverrides({ factory(originalFactory, { inputs, config }) { return originalFactory({ path: '/catalog', - routeRef: convertLegacyRouteRef(rootRouteRef), + routeRef: rootRouteRef, loader: async () => { const { BaseCatalogPage } = await import('../components/CatalogPage'); const filters = inputs.filters.map(filter => filter.get(coreExtensionData.reactElement), ); - return compatWrapper( + return ( {filters}} pagination={config.pagination} - />, + /> ); }, }); @@ -220,7 +217,7 @@ export const catalogEntityPage = PageBlueprint.makeWithOverrides({ ); }; - return compatWrapper(); + return ; }, }); }, diff --git a/plugins/catalog/src/alpha/plugin.tsx b/plugins/catalog/src/alpha/plugin.tsx index ff26d20003..44cf2101b6 100644 --- a/plugins/catalog/src/alpha/plugin.tsx +++ b/plugins/catalog/src/alpha/plugin.tsx @@ -14,7 +14,6 @@ * limitations under the License. */ -import { convertLegacyRouteRefs } from '@backstage/core-compat-api'; import { createFrontendPlugin } from '@backstage/frontend-plugin-api'; import { entityRouteRef } from '@backstage/plugin-catalog-react'; @@ -41,16 +40,16 @@ import contextMenuItems from './contextMenuItems'; export default createFrontendPlugin({ pluginId: 'catalog', info: { packageJson: () => import('../../package.json') }, - routes: convertLegacyRouteRefs({ + routes: { catalogIndex: rootRouteRef, catalogEntity: entityRouteRef, - }), - externalRoutes: convertLegacyRouteRefs({ + }, + externalRoutes: { viewTechDoc: viewTechDocRouteRef, createComponent: createComponentRouteRef, createFromTemplate: createFromTemplateRouteRef, unregisterRedirect: unregisterRedirectRouteRef, - }), + }, extensions: [ ...apis, ...pages, diff --git a/plugins/config-schema/CHANGELOG.md b/plugins/config-schema/CHANGELOG.md index babc867fde..1329d91732 100644 --- a/plugins/config-schema/CHANGELOG.md +++ b/plugins/config-schema/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-config-schema +## 0.1.75 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + +## 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 + ## 0.1.74 ### Patch Changes diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index 3d3f1c258e..46b051aca6 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-config-schema", - "version": "0.1.74", + "version": "0.1.75", "description": "A Backstage plugin that lets you browse the configuration schema of your app", "backstage": { "role": "frontend-plugin", diff --git a/plugins/config-schema/report.api.md b/plugins/config-schema/report.api.md index c78e56aa0e..66c856f4d8 100644 --- a/plugins/config-schema/report.api.md +++ b/plugins/config-schema/report.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 { ApiRef } from '@backstage/core-plugin-api'; +import { ApiRef } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { JSX as JSX_2 } from 'react/jsx-runtime'; import { Observable } from '@backstage/types'; diff --git a/plugins/devtools-backend/CHANGELOG.md b/plugins/devtools-backend/CHANGELOG.md index 3a7677c330..1f3e04d742 100644 --- a/plugins/devtools-backend/CHANGELOG.md +++ b/plugins/devtools-backend/CHANGELOG.md @@ -1,5 +1,52 @@ # @backstage/plugin-devtools-backend +## 0.5.12 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 291bf9d: Added scheduled tasks UI feature for the DevTools plugin +- Updated dependencies + - @backstage/backend-defaults@0.14.0 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/cli-common@0.1.16 + - @backstage/config-loader@1.10.7 + - @backstage/plugin-devtools-common@0.1.20 + +## 0.5.12-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config-loader@1.10.7-next.1 + - @backstage/cli-common@0.1.16-next.2 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-devtools-common@0.1.19 + - @backstage/plugin-permission-common@0.9.3 + +## 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 + ## 0.5.11 ### Patch Changes diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json index 33259ac70c..36a476f4fb 100644 --- a/plugins/devtools-backend/package.json +++ b/plugins/devtools-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools-backend", - "version": "0.5.11", + "version": "0.5.12", "backstage": { "role": "backend-plugin", "pluginId": "devtools", @@ -51,7 +51,7 @@ "@manypkg/get-packages": "^1.1.3", "@yarnpkg/lockfile": "^1.1.0", "@yarnpkg/parsers": "^3.0.0", - "express": "^4.18.1", + "express": "^4.22.0", "express-promise-router": "^4.1.0", "fs-extra": "^11.0.0", "lodash": "^4.17.21", diff --git a/plugins/devtools-backend/src/plugin.ts b/plugins/devtools-backend/src/plugin.ts index d3fefda343..d7ac3ff6d9 100644 --- a/plugins/devtools-backend/src/plugin.ts +++ b/plugins/devtools-backend/src/plugin.ts @@ -20,6 +20,10 @@ import { } from '@backstage/backend-plugin-api'; import { createRouter } from './service/router'; import { devToolsPermissions } from '@backstage/plugin-devtools-common'; +import { + devToolsTaskSchedulerReadPermission, + devToolsTaskSchedulerCreatePermission, +} from '@backstage/plugin-devtools-common/alpha'; /** * DevTools backend plugin @@ -61,7 +65,11 @@ export const devtoolsPlugin = createBackendPlugin({ path: '/health', allow: 'unauthenticated', }); - permissionsRegistry.addPermissions(devToolsPermissions); + permissionsRegistry.addPermissions([ + ...devToolsPermissions, + devToolsTaskSchedulerReadPermission, + devToolsTaskSchedulerCreatePermission, + ]); }, }); }, diff --git a/plugins/devtools-common/CHANGELOG.md b/plugins/devtools-common/CHANGELOG.md index 6eeced0711..baa07b0bb7 100644 --- a/plugins/devtools-common/CHANGELOG.md +++ b/plugins/devtools-common/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/plugin-devtools-common +## 0.1.20 + +### Patch Changes + +- 291bf9d: Added scheduled tasks UI feature for the DevTools plugin + ## 0.1.19 ### Patch Changes diff --git a/plugins/devtools-common/package.json b/plugins/devtools-common/package.json index c442185e14..21d99333c2 100644 --- a/plugins/devtools-common/package.json +++ b/plugins/devtools-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools-common", - "version": "0.1.19", + "version": "0.1.20", "description": "Common functionalities for the devtools plugin", "backstage": { "role": "common-library", @@ -12,10 +12,7 @@ ] }, "publishConfig": { - "access": "public", - "main": "dist/index.cjs.js", - "module": "dist/index.esm.js", - "types": "dist/index.d.ts" + "access": "public" }, "homepage": "https://backstage.io", "repository": { @@ -25,8 +22,23 @@ }, "license": "Apache-2.0", "sideEffects": false, + "exports": { + ".": "./src/index.ts", + "./alpha": "./src/alpha.ts", + "./package.json": "./package.json" + }, "main": "src/index.ts", "types": "src/index.ts", + "typesVersions": { + "*": { + "alpha": [ + "src/alpha.ts" + ], + "package.json": [ + "package.json" + ] + } + }, "files": [ "dist" ], diff --git a/plugins/devtools-common/report-alpha.api.md b/plugins/devtools-common/report-alpha.api.md new file mode 100644 index 0000000000..3c3b7ff89e --- /dev/null +++ b/plugins/devtools-common/report-alpha.api.md @@ -0,0 +1,69 @@ +## API Report File for "@backstage/plugin-devtools-common" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { BasicPermission } from '@backstage/plugin-permission-common'; +import { JsonObject } from '@backstage/types'; + +// @alpha (undocumented) +export const devToolsTaskSchedulerCreatePermission: BasicPermission; + +// @alpha (undocumented) +export const devToolsTaskSchedulerReadPermission: BasicPermission; + +// @alpha (undocumented) +export type ScheduledTasks = { + scheduledTasks?: TaskApiTasksResponse[]; + error?: string; +}; + +// @alpha +export interface TaskApiTasksResponse { + // (undocumented) + pluginId: string; + // (undocumented) + scope: 'global' | 'local'; + // (undocumented) + settings: { + version: number; + } & JsonObject; + // (undocumented) + taskId: string; + // (undocumented) + taskState: + | { + status: 'running'; + startedAt: string; + timesOutAt?: string; + lastRunError?: string; + lastRunEndedAt?: string; + } + | { + status: 'idle'; + startsAt?: string; + lastRunError?: string; + lastRunEndedAt?: string; + } + | null; + // (undocumented) + workerState: + | { + status: 'initial-wait'; + } + | { + status: 'idle'; + } + | { + status: 'running'; + } + | null; +} + +// @alpha (undocumented) +export type TriggerScheduledTask = { + error?: string; +}; + +// (No @packageDocumentation comment for this package) +``` diff --git a/packages/core-plugin-api/src/apis/definitions/alpha.ts b/plugins/devtools-common/src/alpha.ts similarity index 70% rename from packages/core-plugin-api/src/apis/definitions/alpha.ts rename to plugins/devtools-common/src/alpha.ts index e7f6f13fef..984967c4f3 100644 --- a/packages/core-plugin-api/src/apis/definitions/alpha.ts +++ b/plugins/devtools-common/src/alpha.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * 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. @@ -14,9 +14,11 @@ * limitations under the License. */ export { - translationApiRef, - type TranslationApi, - type TranslationFunction, - type TranslationSnapshot, -} from './TranslationApi'; -export { appLanguageApiRef, type AppLanguageApi } from './AppLanguageApi'; + devToolsTaskSchedulerReadPermission, + devToolsTaskSchedulerCreatePermission, +} from './permissions'; +export type { + ScheduledTasks, + TaskApiTasksResponse, + TriggerScheduledTask, +} from './types'; diff --git a/plugins/devtools-common/src/index.ts b/plugins/devtools-common/src/index.ts index be7426394b..88bc0d545f 100644 --- a/plugins/devtools-common/src/index.ts +++ b/plugins/devtools-common/src/index.ts @@ -20,5 +20,19 @@ * @packageDocumentation */ -export * from './types'; -export * from './permissions'; +export type { + ConfigError, + ConfigInfo, + DevToolsInfo, + Endpoint, + ExternalDependency, + PackageDependency, +} from './types'; +export { ExternalDependencyStatus } from './types'; +export { + devToolsAdministerPermission, + devToolsConfigReadPermission, + devToolsExternalDependenciesReadPermission, + devToolsInfoReadPermission, + devToolsPermissions, +} from './permissions'; diff --git a/plugins/devtools-common/src/permissions.ts b/plugins/devtools-common/src/permissions.ts index ba464f7d80..42a7efe777 100644 --- a/plugins/devtools-common/src/permissions.ts +++ b/plugins/devtools-common/src/permissions.ts @@ -48,6 +48,22 @@ export const devToolsExternalDependenciesReadPermission = createPermission({ attributes: { action: 'read' }, }); +/** + * @alpha + */ +export const devToolsTaskSchedulerReadPermission = createPermission({ + name: 'devtools.scheduler.read', + attributes: { action: 'read' }, +}); + +/** + * @alpha + */ +export const devToolsTaskSchedulerCreatePermission = createPermission({ + name: 'devtools.scheduler.trigger', + attributes: { action: 'update' }, +}); + /** * List of all Devtools permissions * diff --git a/plugins/devtools-common/src/types.ts b/plugins/devtools-common/src/types.ts index f1e60d9c2d..dc35188fde 100644 --- a/plugins/devtools-common/src/types.ts +++ b/plugins/devtools-common/src/types.ts @@ -16,7 +16,7 @@ /* We want to maintain the same information as an enum, so we disable the redeclaration warning */ /* eslint-disable @typescript-eslint/no-redeclare */ -import { JsonValue } from '@backstage/types'; +import { JsonObject, JsonValue } from '@backstage/types'; /** @public */ export type Endpoint = { @@ -82,3 +82,54 @@ export type ConfigError = { messages?: string[]; stack?: string; }; + +/** + * The shape of a task definition as returned by the service's REST API. + * This is a duplication of the below: + * @see https://github.com/backstage/backstage/blob/master/packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts + * + * @alpha + */ +export interface TaskApiTasksResponse { + taskId: string; + pluginId: string; + scope: 'global' | 'local'; + settings: { version: number } & JsonObject; + taskState: + | { + status: 'running'; + startedAt: string; + timesOutAt?: string; + lastRunError?: string; + lastRunEndedAt?: string; + } + | { + status: 'idle'; + startsAt?: string; + lastRunError?: string; + lastRunEndedAt?: string; + } + | null; + workerState: + | { + status: 'initial-wait'; + } + | { + status: 'idle'; + } + | { + status: 'running'; + } + | null; +} + +/** @alpha */ +export type ScheduledTasks = { + scheduledTasks?: TaskApiTasksResponse[]; + error?: string; +}; + +/** @alpha */ +export type TriggerScheduledTask = { + error?: string; +}; diff --git a/plugins/devtools/CHANGELOG.md b/plugins/devtools/CHANGELOG.md index dd2da6fdbd..8d842ef87a 100644 --- a/plugins/devtools/CHANGELOG.md +++ b/plugins/devtools/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-devtools +## 0.1.34 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- 291bf9d: Added scheduled tasks UI feature for the DevTools plugin +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/plugin-devtools-common@0.1.20 + - @backstage/core-compat-api@0.5.5 + - @backstage/plugin-permission-react@0.4.39 + +## 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 + ## 0.1.33 ### Patch Changes diff --git a/plugins/devtools/README.md b/plugins/devtools/README.md index 3d82190c2f..07374db5da 100644 --- a/plugins/devtools/README.md +++ b/plugins/devtools/README.md @@ -34,6 +34,12 @@ Lists the configuration being used by your current running Backstage instance. ![Example of Config tab](./docs/devtools-config-tab.png) +### Scheduled Tasks + +Scheduled tasks can be viewed and triggered under the `Scheduled Tasks` tab. [See below to configure](#scheduled-tasks-configuration). + +![Example of Scheduled Tasks tab](./docs/devtools-scheduled-tasks-tab.png) + ## Optional Features The DevTools plugin can be setup with other tabs with additional helpful features. @@ -456,3 +462,14 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ apt-get update && \ apt-get install -y ... iputils-ping ``` + +### Scheduled Tasks Configuration + +Scheduled tasks can be viewed and triggered under the `Scheduled Tasks` tab. You first must add the list of plugins for scheduled tasks to your config: + +```yaml +devTools: + scheduledTasks: + plugins: + - catalog +``` diff --git a/packages/backend-plugin-api/src/alpha/InstanceMetadataService.ts b/plugins/devtools/config.d.ts similarity index 67% rename from packages/backend-plugin-api/src/alpha/InstanceMetadataService.ts rename to plugins/devtools/config.d.ts index a82a261d9d..841bb0b042 100644 --- a/packages/backend-plugin-api/src/alpha/InstanceMetadataService.ts +++ b/plugins/devtools/config.d.ts @@ -13,20 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -/** @alpha */ -export type BackendFeatureMeta = - | { - type: 'plugin'; - pluginId: string; - } - | { - type: 'module'; - pluginId: string; - moduleId: string; +export interface Config { + devTools?: { + /** + * Scheduled tasks configuration + * @visibility frontend + */ + scheduledTasks?: { + /** + * A list of plugin IDs to select from, e.g. ['catalog', 'scaffolder'] + * @visibility frontend + */ + plugins: string[]; }; - -/** @alpha */ -export interface InstanceMetadataService { - getInstalledFeatures: () => BackendFeatureMeta[]; + }; } diff --git a/plugins/devtools/docs/devtools-scheduled-tasks-tab.png b/plugins/devtools/docs/devtools-scheduled-tasks-tab.png new file mode 100644 index 0000000000..90e5e57d83 Binary files /dev/null and b/plugins/devtools/docs/devtools-scheduled-tasks-tab.png differ diff --git a/plugins/devtools/package.json b/plugins/devtools/package.json index 6ba69e0b56..224b5e4b88 100644 --- a/plugins/devtools/package.json +++ b/plugins/devtools/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools", - "version": "0.1.33", + "version": "0.1.34", "backstage": { "role": "frontend-plugin", "pluginId": "devtools", @@ -39,7 +39,8 @@ } }, "files": [ - "dist" + "dist", + "config.d.ts" ], "scripts": { "build": "backstage-cli package build", @@ -61,6 +62,7 @@ "@material-ui/core": "^4.9.13", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "^4.0.0-alpha.57", + "lodash": "^4.17.21", "react-json-view": "^1.21.3", "react-use": "^17.2.4" }, @@ -68,6 +70,7 @@ "@backstage/cli": "workspace:^", "@backstage/dev-utils": "workspace:^", "@testing-library/jest-dom": "^6.0.0", + "@types/lodash": "^4.14.151", "@types/react": "^18.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", @@ -83,5 +86,6 @@ "@types/react": { "optional": true } - } + }, + "configSchema": "config.d.ts" } diff --git a/plugins/devtools/report-alpha.api.md b/plugins/devtools/report-alpha.api.md index c114924f4a..9640158e53 100644 --- a/plugins/devtools/report-alpha.api.md +++ b/plugins/devtools/report-alpha.api.md @@ -8,11 +8,12 @@ import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ApiFactory } from '@backstage/frontend-plugin-api'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; -import { IconComponent } from '@backstage/core-plugin-api'; +import { IconComponent } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; -import { RouteRef } from '@backstage/frontend-plugin-api'; +import { RouteRef } from '@backstage/core-plugin-api'; +import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) const _default: OverridableFrontendPlugin< @@ -45,7 +46,7 @@ const _default: OverridableFrontendPlugin< { title: string; icon: IconComponent; - routeRef: RouteRef; + routeRef: RouteRef_2; }, 'core.nav-item.target', {} @@ -54,7 +55,7 @@ const _default: OverridableFrontendPlugin< params: { title: string; icon: IconComponent; - routeRef: RouteRef; + routeRef: RouteRef_2; }; }>; 'page:devtools': OverridableExtensionDefinition<{ @@ -70,7 +71,7 @@ const _default: OverridableFrontendPlugin< | ExtensionDataRef | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; @@ -81,7 +82,7 @@ const _default: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; loader: () => Promise; - routeRef?: RouteRef; + routeRef?: RouteRef_2; }; }>; } diff --git a/plugins/devtools/report.api.md b/plugins/devtools/report.api.md index e7a42f4377..80297f299b 100644 --- a/plugins/devtools/report.api.md +++ b/plugins/devtools/report.api.md @@ -9,6 +9,7 @@ import { JSX as JSX_2 } from 'react/jsx-runtime'; import { ReactNode } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { TabProps } from '@material-ui/core/Tab'; +import { TaskApiTasksResponse } from '@backstage/plugin-devtools-common/alpha'; // @public (undocumented) export const ConfigContent: () => JSX_2.Element; @@ -43,6 +44,16 @@ export const ExternalDependenciesContent: () => JSX_2.Element; // @public (undocumented) export const InfoContent: () => JSX_2.Element; +// @public (undocumented) +export const ScheduledTaskDetailPanel: ({ + rowData, +}: { + rowData: TaskApiTasksResponse; +}) => JSX_2.Element; + +// @public (undocumented) +export const ScheduledTasksContent: () => JSX_2.Element; + // @public (undocumented) export type SubRoute = { path: string; diff --git a/plugins/devtools/src/alpha/plugin.tsx b/plugins/devtools/src/alpha/plugin.tsx index 0f6d9ec4a1..85021e9857 100644 --- a/plugins/devtools/src/alpha/plugin.tsx +++ b/plugins/devtools/src/alpha/plugin.tsx @@ -24,10 +24,6 @@ import { } from '@backstage/frontend-plugin-api'; import { devToolsApiRef, DevToolsClient } from '../api'; -import { - compatWrapper, - convertLegacyRouteRef, -} from '@backstage/core-compat-api'; import BuildIcon from '@material-ui/icons/Build'; import { rootRouteRef } from '../routes'; @@ -49,11 +45,9 @@ export const devToolsApi = ApiBlueprint.make({ export const devToolsPage = PageBlueprint.make({ params: { path: '/devtools', - routeRef: convertLegacyRouteRef(rootRouteRef), + routeRef: rootRouteRef, loader: () => - import('../components/DevToolsPage').then(m => - compatWrapper(), - ), + import('../components/DevToolsPage').then(m => ), }, }); @@ -61,7 +55,7 @@ export const devToolsPage = PageBlueprint.make({ export const devToolsNavItem = NavItemBlueprint.make({ params: { title: 'DevTools', - routeRef: convertLegacyRouteRef(rootRouteRef), + routeRef: rootRouteRef, icon: BuildIcon, }, }); @@ -71,7 +65,7 @@ export default createFrontendPlugin({ pluginId: 'devtools', info: { packageJson: () => import('../../package.json') }, routes: { - root: convertLegacyRouteRef(rootRouteRef), + root: rootRouteRef, }, extensions: [devToolsApi, devToolsPage, devToolsNavItem], }); diff --git a/plugins/devtools/src/api/DevToolsApi.ts b/plugins/devtools/src/api/DevToolsApi.ts index ff58a4ea16..4fc579ee06 100644 --- a/plugins/devtools/src/api/DevToolsApi.ts +++ b/plugins/devtools/src/api/DevToolsApi.ts @@ -20,6 +20,10 @@ import { DevToolsInfo, ExternalDependency, } from '@backstage/plugin-devtools-common'; +import { + ScheduledTasks, + TriggerScheduledTask, +} from '@backstage/plugin-devtools-common/alpha'; export const devToolsApiRef = createApiRef({ id: 'plugin.devtools.service', @@ -29,4 +33,9 @@ export interface DevToolsApi { getConfig(): Promise; getExternalDependencies(): Promise; getInfo(): Promise; + getScheduledTasksByPlugin(plugin: string): Promise; + triggerScheduledTask( + plugin: string, + taskId: string, + ): Promise; } diff --git a/plugins/devtools/src/api/DevToolsClient.ts b/plugins/devtools/src/api/DevToolsClient.ts index e0b387a6ca..cd4f53fa81 100644 --- a/plugins/devtools/src/api/DevToolsClient.ts +++ b/plugins/devtools/src/api/DevToolsClient.ts @@ -20,6 +20,10 @@ import { DevToolsInfo, ExternalDependency, } from '@backstage/plugin-devtools-common'; +import { + ScheduledTasks, + TriggerScheduledTask, +} from '@backstage/plugin-devtools-common/alpha'; import { ResponseError } from '@backstage/errors'; import { DevToolsApi } from './DevToolsApi'; @@ -42,6 +46,45 @@ export class DevToolsClient implements DevToolsApi { return configInfo; } + public async getScheduledTasksByPlugin( + plugin: string, + ): Promise { + const baseUrl = `${await this.discoveryApi.getBaseUrl(plugin)}/`; + const url = new URL('.backstage/scheduler/v1/tasks', baseUrl); + + const response = await this.fetchApi.fetch(url.toString()); + + if (!response.ok) { + throw await ResponseError.fromResponse(response); + } + + const scheduledTasks = await response.json(); + return { + scheduledTasks: scheduledTasks.tasks, + }; + } + + public async triggerScheduledTask( + plugin: string, + taskId: string, + ): Promise { + const baseUrl = `${await this.discoveryApi.getBaseUrl(plugin)}/`; + const url = new URL( + `.backstage/scheduler/v1/tasks/${taskId}/trigger`, + baseUrl, + ); + + const response = await this.fetchApi.fetch(url.toString(), { + method: 'POST', + }); + + if (!response.ok) { + throw await ResponseError.fromResponse(response); + } + + return response.json() as Promise; + } + public async getExternalDependencies(): Promise< ExternalDependency[] | undefined > { diff --git a/plugins/devtools/src/components/Content/ScheduledTasksContent/ScheduledTaskDetailedPanel.tsx b/plugins/devtools/src/components/Content/ScheduledTasksContent/ScheduledTaskDetailedPanel.tsx new file mode 100644 index 0000000000..a29e1eb10e --- /dev/null +++ b/plugins/devtools/src/components/Content/ScheduledTasksContent/ScheduledTaskDetailedPanel.tsx @@ -0,0 +1,104 @@ +/* + * 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 { TaskApiTasksResponse } from '@backstage/plugin-devtools-common/alpha'; +import Grid from '@material-ui/core/Grid'; +import Typography from '@material-ui/core/Typography'; +import Box from '@material-ui/core/Box'; +import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; +import Alert from '@material-ui/lab/Alert'; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + detailPanel: { + padding: theme.spacing(2), + backgroundColor: theme.palette.background.default, + }, + detailLabel: { + fontWeight: 'bold', + marginRight: theme.spacing(1), + }, + errorIcon: { + color: theme.palette.error.main, + marginRight: theme.spacing(1), + fontSize: '1.2rem', + }, + detailPanelAlert: { + marginBottom: theme.spacing(2), + }, + }), +); + +/** @public */ +export const ScheduledTaskDetailPanel = ({ + rowData, +}: { + rowData: TaskApiTasksResponse; +}) => { + const classes = useStyles(); + const lastRunError = rowData.taskState?.lastRunError; + + const DetailItem = ({ title, value }: { title: string; value: any }) => ( + <> + + + {title}: + + + + + {value || 'N/A'} + + + + ); + + return ( + + {lastRunError && ( + + Last Run Error: {lastRunError} + + )} + + + + + + + + + + ); +}; diff --git a/plugins/devtools/src/components/Content/ScheduledTasksContent/ScheduledTasksContent.tsx b/plugins/devtools/src/components/Content/ScheduledTasksContent/ScheduledTasksContent.tsx new file mode 100644 index 0000000000..2d4abd56ef --- /dev/null +++ b/plugins/devtools/src/components/Content/ScheduledTasksContent/ScheduledTasksContent.tsx @@ -0,0 +1,307 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useState } from 'react'; +import Box from '@material-ui/core/Box'; +import Typography from '@material-ui/core/Typography'; +import IconButton from '@material-ui/core/IconButton'; +import Tooltip from '@material-ui/core/Tooltip'; +import Autocomplete from '@material-ui/lab/Autocomplete'; +import TextField from '@material-ui/core/TextField'; +import { makeStyles, createStyles, Theme } from '@material-ui/core/styles'; +import { + ErrorPanel, + Progress, + Table, + TableColumn, +} from '@backstage/core-components'; +import Alert from '@material-ui/lab/Alert'; +import { useScheduledTasks, useTriggerScheduledTask } from '../../../hooks'; +import { TaskApiTasksResponse } from '@backstage/plugin-devtools-common/alpha'; +import { alertApiRef, configApiRef, useApi } from '@backstage/core-plugin-api'; +import RefreshIcon from '@material-ui/icons/Refresh'; +import NightsStay from '@material-ui/icons/NightsStay'; +import ErrorIcon from '@material-ui/icons/Error'; +import CircularProgress from '@material-ui/core/CircularProgress'; +import { ScheduledTaskDetailPanel } from './ScheduledTaskDetailedPanel'; +import { RequirePermission } from '@backstage/plugin-permission-react'; +import { devToolsTaskSchedulerCreatePermission } from '@backstage/plugin-devtools-common/alpha'; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + paperStyle: { + display: 'flex', + marginBottom: theme.spacing(2), + }, + flexContainer: { + display: 'flex', + flexDirection: 'row', + padding: 0, + }, + formControl: { + minWidth: 240, + marginBottom: theme.spacing(2), + }, + detailPanel: { + padding: theme.spacing(2), + backgroundColor: theme.palette.background.default, + }, + detailLabel: { + fontWeight: 'bold', + marginRight: theme.spacing(1), + }, + errorIcon: { + color: theme.palette.error.main, + marginRight: theme.spacing(1), + fontSize: '1.2rem', + }, + detailPanelAlert: { + marginBottom: theme.spacing(2), + }, + }), +); + +const StatusDisplay = ({ + icon, + text, +}: { + icon: React.ReactNode; + text: string; +}) => ( + + {icon} + + {text} + + +); + +/** @public */ +export const ScheduledTasksContent = () => { + const classes = useStyles(); + const configApi = useApi(configApiRef); + const alertApi = useApi(alertApiRef); + const plugins = + configApi.getOptionalStringArray('devTools.scheduledTasks.plugins') || []; + const [selectedPlugin, setSelectedPlugin] = useState(plugins[0] || ''); + const { scheduledTasks, loading, error } = useScheduledTasks(selectedPlugin); + const { triggerTask, isTriggering, triggerError } = useTriggerScheduledTask(); + + const [inputValue, setInputValue] = useState(''); + + const handleAutocompleteChange = (_event: any, newValue: string | null) => { + setSelectedPlugin(newValue || ''); + }; + + const handleCommitChange = () => { + if (inputValue !== selectedPlugin) { + setSelectedPlugin(inputValue); + } + }; + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'Enter') { + handleCommitChange(); + // Prevent Autocomplete's default behavior (which might select a filtered item) + event.preventDefault(); + event.stopPropagation(); + } + }; + + if (!plugins || plugins.length === 0) { + return ( + + No plugins configured for scheduled tasks. Please configure + `devTools.scheduledTasks.plugins` in app-config.yaml. + + ); + } + + const columns: TableColumn[] = [ + { + title: 'Task ID', + field: 'taskId', + width: '35%', + render: (rowData: TaskApiTasksResponse) => { + const errorIconStyle: React.CSSProperties = { + color: '#f44336', + marginRight: '8px', + fontSize: '1.2rem', + verticalAlign: 'middle', + }; + + return ( + + {rowData.taskState?.lastRunError && ( + + )} + {rowData.taskId} + + ); + }, + }, + { + title: 'Status', + field: 'taskState.status', + width: '15%', + render: (rowData: TaskApiTasksResponse) => { + const status = rowData.taskState?.status; + + if (status === 'idle') { + return ( + } text="Idle" /> + ); + } + + if (status === 'running') { + return ( + } + text="Running" + /> + ); + } + + return {status || 'N/A'}; + }, + }, + { + title: 'Last Run', + field: 'taskState.lastRunEndedAt', + width: '25%', + render: (rowData: TaskApiTasksResponse) => + rowData.taskState?.lastRunEndedAt + ? new Date(rowData.taskState.lastRunEndedAt).toLocaleString() + : 'N/A', + }, + { + title: 'Next Run', + width: '15%', + render: (rowData: TaskApiTasksResponse) => + rowData.taskState?.status === 'idle' && rowData.taskState.startsAt + ? new Date(rowData.taskState.startsAt).toLocaleString() + : 'N/A', + }, + { + title: 'Actions', + render: (rowData: TaskApiTasksResponse) => ( + + + { + triggerTask(selectedPlugin, rowData.taskId); + if (triggerError) { + alertApi.post({ + message: `Error triggering task ${rowData.taskId}: ${error}`, + severity: 'error', + }); + } else { + alertApi.post({ + message: `Successfully triggered task ${rowData.taskId}`, + severity: 'success', + }); + } + }} + > + + + + + ), + sorting: false, + width: '10%', + }, + ]; + + return ( + + { + setInputValue(newInputValue); + }} + renderInput={params => ( + + )} + /> + + {loading && } + + {error && ( + + + The plugin ID "{selectedPlugin}" doesn't have any scheduled tasks or + may contain a typo. + + + Please verify: + +
    +
  • + + The plugin ID is spelled correctly + +
  • +
  • + + The plugin has registered scheduled tasks + +
  • +
+
+ )} + + {!loading && !error && ( + + No scheduled tasks found for {selectedPlugin}. + + } + detailPanel={({ rowData }) => { + return ; + }} + /> + )} + + ); +}; diff --git a/plugins/devtools/src/components/Content/ScheduledTasksContent/fixtures/scheduledTasksErrors.json b/plugins/devtools/src/components/Content/ScheduledTasksContent/fixtures/scheduledTasksErrors.json new file mode 100644 index 0000000000..89f81b740d --- /dev/null +++ b/plugins/devtools/src/components/Content/ScheduledTasksContent/fixtures/scheduledTasksErrors.json @@ -0,0 +1,205 @@ +{ + "tasks": [ + { + "taskId": "cool-provider", + "pluginId": "catalog", + "scope": "global", + "settings": { + "version": 2, + "cadence": "PT24H", + "timeoutAfterDuration": "PT60M" + }, + "taskState": { + "status": "running", + "startedAt": "2025-10-31T21:02:36.461+00:00", + "timesOutAt": "2025-10-31T22:02:36.461+00:00", + "lastRunEndedAt": "2025-10-31T19:57:15.674+00:00", + "lastRunError": "{\"name\":\"error\",\"message\":\"insert into \\\"refresh_state\\\" (\\\"entity_id\\\", \\\"entity_ref\\\", \\\"errors\\\", \\\"last_discovery_at\\\", \\\"location_key\\\", \\\"next_update_at\\\", \\\"unprocessed_entity\\\", \\\"unprocessed_hash\\\") values ($1, $2, $3, CURRENT_TIMESTAMP, $4, CURRENT_TIMESTAMP, $5, $6), ($7, $8, $9, CURRENT_TIMESTAMP, $10, CURRENT_TIMESTAMP, $11, $12), ($13, $14, $15, CURRENT_TIMESTAMP, $16, CURRENT_TIMESTAMP, $17, $18), ($19, $20, $21, CURRENT_TIMESTAMP, $22, CURRENT_TIMESTAMP, $23, $24), ($25, $26, $27, CURRENT_TIMESTAMP, $28, CURRENT_TIMESTAMP, $29, $30), ($31, $32, $33, CURRENT_TIMESTAMP, $34, CURRENT_TIMESTAMP, $35, $36), ($37, $38, $39, CURRENT_TIMESTAMP, $40, CURRENT_TIMESTAMP, $41, $42), ($43, $44, $45, CURRENT_TIMESTAMP, $46, CURRENT_TIMESTAMP, $47, $48), ($49, $50, $51, CURRENT_TIMESTAMP, $52, CURRENT_TIMESTAMP, $53, $54), ($55, $56, $57, CURRENT_TIMESTAMP, $58, CURRENT_TIMESTAMP, $59, $60), ($61, $62, $63, CURRENT_TIMESTAMP, $64, CURRENT_TIMESTAMP, $65, $66), ($67, $68, $69, CURRENT_TIMESTAMP, $70, CURRENT_TIMESTAMP, $71, $72), ($73, $74, $75, CURRENT_TIMESTAMP, $76, CURRENT_TIMESTAMP, $77, $78), ($79, $80, $81, CURRENT_TIMESTAMP, $82, CURRENT_TIMESTAMP, $83, $84), ($85, $86, $87, CURRENT_TIMESTAMP, $88, CURRENT_TIMESTAMP, $89, $90), ($91, $92, $93, CURRENT_TIMESTAMP, $94, CURRENT_TIMESTAMP, $95, $96), ($97, $98, $99, CURRENT_TIMESTAMP, $100, CURRENT_TIMESTAMP, $101, $102), ($103, $104, $105, CURRENT_TIMESTAMP, $106, CURRENT_TIMESTAMP, $107, $108), ($109, $110, $111, CURRENT_TIMESTAMP, $112, CURRENT_TIMESTAMP, $113, $114), ($115, $116, $117, CURRENT_TIMESTAMP, $118, CURRENT_TIMESTAMP, $119, $120), ($121, $122, $123, CURRENT_TIMESTAMP, $124, CURRENT_TIMESTAMP, $125, $126), ($127, $128, $129, CURRENT_TIMESTAMP, $130, CURRENT_TIMESTAMP, $131, $132), ($133, $134, $135, CURRENT_TIMESTAMP, $136, CURRENT_TIMESTAMP, $137, $138), ($139, $140, $141, CURRENT_TIMESTAMP, $142, CURRENT_TIMESTAMP, $143, $144), ($145, $146, $147, CURRENT_TIMESTAMP, $148, CURRENT_TIMESTAMP, $149, $150), ($151, $152, $153, CURRENT_TIMESTAMP, $154, CURRENT_TIMESTAMP, $155, $156), ($157, $158, $159, CURRENT_TIMESTAMP, $160, CURRENT_TIMESTAMP, $161, $162), ($163, $164, $165, CURRENT_TIMESTAMP, $166, CURRENT_TIMESTAMP, $167, $168), ($169, $170, $171, CURRENT_TIMESTAMP, $172, CURRENT_TIMESTAMP, $173, $174), ($175, $176, $177, CURRENT_TIMESTAMP, $178, CURRENT_TIMESTAMP, $179, $180), ($181, $182, $183, CURRENT_TIMESTAMP, $184, CURRENT_TIMESTAMP, $185, $186), ($187, $188, $189, CURRENT_TIMESTAMP, $190, CURRENT_TIMESTAMP, $191, $192), ($193, $194, $195, CURRENT_TIMESTAMP, $196, CURRENT_TIMESTAMP, $197, $198), ($199, $200, $201, CURRENT_TIMESTAMP, $202, CURRENT_TIMESTAMP, $203, $204), ($205, $206, $207, CURRENT_TIMESTAMP, $208, CURRENT_TIMESTAMP, $209, $210), ($211, $212, $213, CURRENT_TIMESTAMP, $214, CURRENT_TIMESTAMP, $215, $216), ($217, $218, $219, CURRENT_TIMESTAMP, $220, CURRENT_TIMESTAMP, $221, $222), ($223, $224, $225, CURRENT_TIMESTAMP, $226, CURRENT_TIMESTAMP, $227, $228), ($229, $230, $231, CURRENT_TIMESTAMP, $232, CURRENT_TIMESTAMP, $233, $234), ($235, $236, $237, CURRENT_TIMESTAMP, $238, CURRENT_TIMESTAMP, $239, $240), ($241, $242, $243, CURRENT_TIMESTAMP, $244, CURRENT_TIMESTAMP, $245, $246), ($247, $248, $249, CURRENT_TIMESTAMP, $250, CURRENT_TIMESTAMP, $251, $252), ($253, $254, $255, CURRENT_TIMESTAMP, $256, CURRENT_TIMESTAMP, $257, $258), ($259, $260, $261, CURRENT_TIMESTAMP, $262, CURRENT_TIMESTAMP, $263, $264), ($265, $266, $267, CURRENT_TIMESTAMP, $268, CURRENT_TIMESTAMP, $269, $270), ($271, $272, $273, CURRENT_TIMESTAMP, $274, CURRENT_TIMESTAMP, $275, $276), ($277, $278, $279, CURRENT_TIMESTAMP, $280, CURRENT_TIMESTAMP, $281, $282), ($283, $284, $285, CURRENT_TIMESTAMP, $286, CURRENT_TIMESTAMP, $287, $288), ($289, $290, $291, CURRENT_TIMESTAMP, $292, CURRENT_TIMESTAMP, $293, $294), ($295, $296, $297, CURRENT_TIMESTAMP, $298, CURRENT_TIMESTAMP, $299, $300) - value too long for type character varying(255)\",\"length\":99,\"severity\":\"ERROR\",\"code\":\"22001\",\"file\":\"varchar.c\",\"line\":\"637\",\"routine\":\"varchar\"}" + }, + "workerState": { + "status": "running" + } + }, + { + "taskId": "some-provider", + "pluginId": "catalog", + "scope": "global", + "settings": { + "version": 2, + "cadence": "PT24H", + "timeoutAfterDuration": "PT24H" + }, + "taskState": { + "status": "idle", + "startsAt": "2025-11-01T21:02:34.850+00:00", + "lastRunEndedAt": "2025-10-31T21:02:35.556+00:00" + }, + "workerState": { + "status": "idle" + } + }, + { + "taskId": "three-provider", + "pluginId": "catalog", + "scope": "global", + "settings": { + "version": 2, + "cadence": "PT30M", + "timeoutAfterDuration": "PT2M" + }, + "taskState": { + "status": "idle", + "startsAt": "2025-10-31T21:16:04.145+00:00", + "lastRunEndedAt": "2025-10-31T20:46:09.184+00:00", + "lastRunError": "{\"name\":\"TypeError\",\"message\":\"String.prototype.replaceAll called with a non-global RegExp argument\"}" + }, + "workerState": { + "status": "idle" + } + }, + { + "taskId": "a-provider", + "pluginId": "catalog", + "scope": "global", + "settings": { + "version": 2, + "cadence": "PT60M", + "timeoutAfterDuration": "PT24H" + }, + "taskState": { + "status": "idle", + "startsAt": "2025-10-31T21:16:04.152+00:00", + "lastRunEndedAt": "2025-10-31T20:16:08.713+00:00" + }, + "workerState": { + "status": "idle" + } + }, + { + "taskId": "github-provider:provider123:refresh", + "pluginId": "catalog", + "scope": "global", + "settings": { + "version": 2, + "cadence": "PT24H", + "timeoutAfterDuration": "PT1H" + }, + "taskState": { + "status": "idle", + "startsAt": "2025-11-01T18:16:04.153+00:00", + "lastRunEndedAt": "2025-10-31T18:18:56.176+00:00" + }, + "workerState": { + "status": "idle" + } + }, + { + "taskId": "github-provider:provider123:refresh", + "pluginId": "catalog", + "scope": "global", + "settings": { + "version": 2, + "cadence": "PT24H", + "timeoutAfterDuration": "PT1H" + }, + "taskState": { + "status": "idle", + "startsAt": "2025-11-01T18:16:04.156+00:00", + "lastRunEndedAt": "2025-10-31T18:16:04.264+00:00" + }, + "workerState": { + "status": "idle" + } + }, + { + "taskId": "github-provider:provider234:refresh", + "pluginId": "catalog", + "scope": "global", + "settings": { + "version": 2, + "cadence": "PT24H", + "timeoutAfterDuration": "PT1H" + }, + "taskState": { + "status": "idle", + "startsAt": "2025-11-01T18:16:04.157+00:00", + "lastRunEndedAt": "2025-10-31T18:16:08.564+00:00" + }, + "workerState": { + "status": "idle" + } + }, + { + "taskId": "github-provider:provider567:refresh", + "pluginId": "catalog", + "scope": "global", + "settings": { + "version": 2, + "cadence": "PT24H", + "timeoutAfterDuration": "PT1H" + }, + "taskState": { + "status": "idle", + "startsAt": "2025-11-01T18:16:04.154+00:00", + "lastRunEndedAt": "2025-10-31T18:16:06.978+00:00" + }, + "workerState": { + "status": "idle" + } + }, + { + "taskId": "github-provider:provider910:refresh", + "pluginId": "catalog", + "scope": "global", + "settings": { + "version": 2, + "cadence": "PT24H", + "timeoutAfterDuration": "PT1H" + }, + "taskState": { + "status": "idle", + "startsAt": "2025-11-01T18:16:04.160+00:00", + "lastRunEndedAt": "2025-10-31T18:16:08.573+00:00" + }, + "workerState": { + "status": "idle" + } + }, + { + "taskId": "github-provider:provider000:refresh", + "pluginId": "catalog", + "scope": "global", + "settings": { + "version": 2, + "cadence": "PT24H", + "timeoutAfterDuration": "PT1H" + }, + "taskState": { + "status": "idle", + "startsAt": "2025-11-01T18:16:04.161+00:00", + "lastRunEndedAt": "2025-10-31T18:18:54.339+00:00" + }, + "workerState": { + "status": "idle" + } + }, + { + "taskId": "catalog_orphan_cleanup", + "pluginId": "catalog", + "scope": "global", + "settings": { + "version": 2, + "cadence": "PT30S", + "timeoutAfterDuration": "PT24S" + }, + "taskState": { + "status": "idle", + "startsAt": "2025-10-31T21:06:35.672+00:00", + "lastRunEndedAt": "2025-10-31T21:06:09.106+00:00" + }, + "workerState": { + "status": "idle" + } + } + ] +} diff --git a/plugins/devtools/src/components/Content/ScheduledTasksContent/index.ts b/plugins/devtools/src/components/Content/ScheduledTasksContent/index.ts new file mode 100644 index 0000000000..dff8bcf653 --- /dev/null +++ b/plugins/devtools/src/components/Content/ScheduledTasksContent/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { ScheduledTasksContent } from './ScheduledTasksContent'; +export { ScheduledTaskDetailPanel } from './ScheduledTaskDetailedPanel'; diff --git a/plugins/devtools/src/components/Content/index.ts b/plugins/devtools/src/components/Content/index.ts index f5bd64f7c0..964e7116d9 100644 --- a/plugins/devtools/src/components/Content/index.ts +++ b/plugins/devtools/src/components/Content/index.ts @@ -17,3 +17,4 @@ export * from './ConfigContent'; export * from './InfoContent'; export * from './ExternalDependenciesContent'; +export * from './ScheduledTasksContent'; diff --git a/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx b/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx index b4e5d3db5b..d706fe44e7 100644 --- a/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx +++ b/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx @@ -18,11 +18,12 @@ import { devToolsConfigReadPermission, devToolsInfoReadPermission, } from '@backstage/plugin-devtools-common'; - +import { devToolsTaskSchedulerReadPermission } from '@backstage/plugin-devtools-common/alpha'; import { ConfigContent } from '../Content/ConfigContent'; import { DevToolsLayout } from '../DevToolsLayout'; import { InfoContent } from '../Content/InfoContent'; import { RequirePermission } from '@backstage/plugin-permission-react'; +import { ScheduledTasksContent } from '../Content/ScheduledTasksContent'; /** @public */ export const DefaultDevToolsPage = () => ( @@ -37,5 +38,10 @@ export const DefaultDevToolsPage = () => ( + + + + + ); diff --git a/plugins/devtools/src/hooks/index.ts b/plugins/devtools/src/hooks/index.ts index bb2e83d06f..105d03fb68 100644 --- a/plugins/devtools/src/hooks/index.ts +++ b/plugins/devtools/src/hooks/index.ts @@ -17,3 +17,5 @@ export { useConfig } from './useConfig'; export { useExternalDependencies } from './useExternalDependencies'; export { useInfo } from './useInfo'; +export { useScheduledTasks } from './useScheduledTasks'; +export { useTriggerScheduledTask } from './useTriggerScheduledTask'; diff --git a/plugins/devtools/src/hooks/useScheduledTasks.ts b/plugins/devtools/src/hooks/useScheduledTasks.ts new file mode 100644 index 0000000000..2797526dec --- /dev/null +++ b/plugins/devtools/src/hooks/useScheduledTasks.ts @@ -0,0 +1,44 @@ +/* + * 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 { devToolsApiRef } from '../api'; +import { useApi } from '@backstage/core-plugin-api'; +import useAsync from 'react-use/esm/useAsync'; + +export const useScheduledTasks = (plugin: string) => { + const api = useApi(devToolsApiRef); + + const { + value, + loading, + error: asyncError, + } = useAsync(async () => { + return api.getScheduledTasksByPlugin(plugin); + }, [api, plugin]); + + if (asyncError) { + return { + scheduledTasks: undefined, + loading: false, + error: asyncError.message, + }; + } + + return { + scheduledTasks: value?.scheduledTasks, + loading, + error: value?.error, + }; +}; diff --git a/plugins/devtools/src/hooks/useTriggerScheduledTask.ts b/plugins/devtools/src/hooks/useTriggerScheduledTask.ts new file mode 100644 index 0000000000..d26b7a8025 --- /dev/null +++ b/plugins/devtools/src/hooks/useTriggerScheduledTask.ts @@ -0,0 +1,46 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { useState, useCallback } from 'react'; +import { devToolsApiRef } from '../api'; +import { useApi } from '@backstage/core-plugin-api'; + +export const useTriggerScheduledTask = () => { + const api = useApi(devToolsApiRef); + const [isTriggering, setIsTriggering] = useState(false); + const [error, setError] = useState(); + + const triggerTask = useCallback( + async (plugin: string, taskId: string) => { + setIsTriggering(true); + setError(undefined); + + try { + await api.triggerScheduledTask(plugin, taskId); + } catch (e) { + setError(e); + } finally { + setIsTriggering(false); + } + }, + [api], + ); + + return { + triggerTask, + isTriggering, + triggerError: error?.message, + }; +}; diff --git a/plugins/events-backend-module-aws-sqs/CHANGELOG.md b/plugins/events-backend-module-aws-sqs/CHANGELOG.md index 8d34a39490..23eeee8fcd 100644 --- a/plugins/events-backend-module-aws-sqs/CHANGELOG.md +++ b/plugins/events-backend-module-aws-sqs/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-events-backend-module-aws-sqs +## 0.4.18 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## 0.4.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## 0.4.18-next.0 + +### Patch Changes + +- 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 + ## 0.4.17 ### Patch Changes diff --git a/plugins/events-backend-module-aws-sqs/package.json b/plugins/events-backend-module-aws-sqs/package.json index c98f44f78c..dbcca544dd 100644 --- a/plugins/events-backend-module-aws-sqs/package.json +++ b/plugins/events-backend-module-aws-sqs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-aws-sqs", - "version": "0.4.17", + "version": "0.4.18", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-azure/CHANGELOG.md b/plugins/events-backend-module-azure/CHANGELOG.md index 4dfc6d135d..07db604cc3 100644 --- a/plugins/events-backend-module-azure/CHANGELOG.md +++ b/plugins/events-backend-module-azure/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-events-backend-module-azure +## 0.2.27 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## 0.2.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 0.2.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + ## 0.2.26 ### Patch Changes diff --git a/plugins/events-backend-module-azure/package.json b/plugins/events-backend-module-azure/package.json index ad3dd79847..e0af5fda5c 100644 --- a/plugins/events-backend-module-azure/package.json +++ b/plugins/events-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-azure", - "version": "0.2.26", + "version": "0.2.27", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md index 8ec30a854c..4acd9bf71d 100644 --- a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-events-backend-module-bitbucket-cloud +## 0.2.27 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## 0.2.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 0.2.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + ## 0.2.26 ### Patch Changes diff --git a/plugins/events-backend-module-bitbucket-cloud/package.json b/plugins/events-backend-module-bitbucket-cloud/package.json index 7541186ba2..cca7630e0d 100644 --- a/plugins/events-backend-module-bitbucket-cloud/package.json +++ b/plugins/events-backend-module-bitbucket-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-bitbucket-cloud", - "version": "0.2.26", + "version": "0.2.27", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-bitbucket-server/CHANGELOG.md b/plugins/events-backend-module-bitbucket-server/CHANGELOG.md index 8af8640878..3d602aebb2 100644 --- a/plugins/events-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/events-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-events-backend-module-bitbucket-server +## 0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## 0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 0.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + ## 0.1.7 ### Patch Changes diff --git a/plugins/events-backend-module-bitbucket-server/package.json b/plugins/events-backend-module-bitbucket-server/package.json index 8686ed7412..452b3262a0 100644 --- a/plugins/events-backend-module-bitbucket-server/package.json +++ b/plugins/events-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-bitbucket-server", - "version": "0.1.7", + "version": "0.1.8", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-gerrit/CHANGELOG.md b/plugins/events-backend-module-gerrit/CHANGELOG.md index 7dc39f98b8..4c0750ad67 100644 --- a/plugins/events-backend-module-gerrit/CHANGELOG.md +++ b/plugins/events-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-events-backend-module-gerrit +## 0.2.27 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## 0.2.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 0.2.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/plugin-events-node@0.4.18-next.0 + ## 0.2.26 ### Patch Changes diff --git a/plugins/events-backend-module-gerrit/package.json b/plugins/events-backend-module-gerrit/package.json index 482136ffe1..3d79d8805c 100644 --- a/plugins/events-backend-module-gerrit/package.json +++ b/plugins/events-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-gerrit", - "version": "0.2.26", + "version": "0.2.27", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-github/CHANGELOG.md b/plugins/events-backend-module-github/CHANGELOG.md index df3672dc00..edff58b374 100644 --- a/plugins/events-backend-module-github/CHANGELOG.md +++ b/plugins/events-backend-module-github/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-events-backend-module-github +## 0.4.7 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## 0.4.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## 0.4.7-next.0 + +### Patch Changes + +- 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 + ## 0.4.6 ### Patch Changes diff --git a/plugins/events-backend-module-github/package.json b/plugins/events-backend-module-github/package.json index c615eaea07..83513f30df 100644 --- a/plugins/events-backend-module-github/package.json +++ b/plugins/events-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-github", - "version": "0.4.6", + "version": "0.4.7", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-gitlab/CHANGELOG.md b/plugins/events-backend-module-gitlab/CHANGELOG.md index 34aae805b0..b97a60c140 100644 --- a/plugins/events-backend-module-gitlab/CHANGELOG.md +++ b/plugins/events-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-events-backend-module-gitlab +## 0.3.8 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## 0.3.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + +## 0.3.8-next.0 + +### Patch Changes + +- 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 + ## 0.3.7 ### Patch Changes diff --git a/plugins/events-backend-module-gitlab/package.json b/plugins/events-backend-module-gitlab/package.json index 744d53d6c7..e0d509fa3b 100644 --- a/plugins/events-backend-module-gitlab/package.json +++ b/plugins/events-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-gitlab", - "version": "0.3.7", + "version": "0.3.8", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-google-pubsub/CHANGELOG.md b/plugins/events-backend-module-google-pubsub/CHANGELOG.md index da7f475c34..d07530ac08 100644 --- a/plugins/events-backend-module-google-pubsub/CHANGELOG.md +++ b/plugins/events-backend-module-google-pubsub/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-events-backend-module-google-pubsub +## 0.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## 0.1.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.1.7-next.0 + +### Patch Changes + +- 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 + ## 0.1.6 ### Patch Changes diff --git a/plugins/events-backend-module-google-pubsub/package.json b/plugins/events-backend-module-google-pubsub/package.json index daed1fe289..d5a97f3223 100644 --- a/plugins/events-backend-module-google-pubsub/package.json +++ b/plugins/events-backend-module-google-pubsub/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-google-pubsub", - "version": "0.1.6", + "version": "0.1.7", "description": "The google-pubsub backend module for the events plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/events-backend-module-kafka/CHANGELOG.md b/plugins/events-backend-module-kafka/CHANGELOG.md index 9a59aeeeeb..3edfcf9487 100644 --- a/plugins/events-backend-module-kafka/CHANGELOG.md +++ b/plugins/events-backend-module-kafka/CHANGELOG.md @@ -1,5 +1,40 @@ # @backstage/plugin-events-backend-module-kafka +## 0.2.0 + +### Minor Changes + +- 2c74ea9: Added support for multiple named instances in `kafkaConsumingEventPublisher` configuration. The previous single configuration format is still supported for backward compatibility. +- 2c74ea9: Added `KafkaPublishingEventConsumer` to support sending Backstage events to Kafka topics. + + This addition enables Backstage to publish events to external Kafka systems, complementing the existing ability to receive events from Kafka. This allows for better integration with external systems that rely on Kafka for event streaming. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## 0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## 0.1.6-next.0 + +### Patch Changes + +- 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 + ## 0.1.5 ### Patch Changes diff --git a/plugins/events-backend-module-kafka/README.md b/plugins/events-backend-module-kafka/README.md index dbc5bf5607..f77b5d2f21 100644 --- a/plugins/events-backend-module-kafka/README.md +++ b/plugins/events-backend-module-kafka/README.md @@ -2,65 +2,107 @@ Welcome to the `events-backend-module-kafka` backend module! -This package is a module for the `events-backend` backend plugin and extends the events system with an `KafkaConsumingEventPublisher.` +This package is a module for the `events-backend` backend plugin and extends the events system with a `KafkaConsumingEventPublisher` and `KafkaPublishingEventConsumer` -This event publisher will allow you to receive events from an Kafka queue and will publish these to the used `EventsService` implementation. +This module provides two-way integration with Kafka: + +- **KafkaConsumingEventPublisher**: Receives events from Kafka queues and publishes them to the Backstage events system +- **KafkaPublishingEventConsumer**: Consumes events from Backstage and publishes them to Kafka queues ## Configuration -To set up Kafka queues, you need to configure the following values: +To set up Kafka integration, you need to configure one or both of the following components: + +### KafkaConsumingEventPublisher Configuration + +To receive events from Kafka queues and publish them to Backstage: ```yaml events: modules: kafka: kafkaConsumingEventPublisher: - clientId: your-client-id # (Required) Client ID used by Backstage to identify when connecting to the Kafka cluster. - brokers: # (Required) List of brokers in the Kafka cluster to connect to. - - broker1 - - broker2 - topics: - - topic: 'backstage.topic' # (Required) Replace with actual topic name as expected by subscribers - kafka: - topics: # (Required) The Kafka topics to subscribe to. - - topic1 - groupId: your-group-id # (Required) The GroupId to be used by the topic consumers. + production: # Instance name, will be included in logs + clientId: your-client-id # (Required) Client ID used by Backstage to identify when connecting to the Kafka cluster. + brokers: # (Required) List of brokers in the Kafka cluster to connect to. + - broker1 + - broker2 + topics: + - topic: 'backstage.topic' # (Required) Replace with actual topic name as expected by subscribers + kafka: + topics: # (Required) The Kafka topics to subscribe to. + - topic1 + groupId: your-group-id # (Required) The GroupId to be used by the topic consumers. +``` + +### KafkaPublishingEventConsumer Configuration + +To publish events from Backstage to Kafka queues, you can configure the `KafkaPublishingEventConsumer`: + +```yaml +events: + modules: + kafka: + kafkaPublishingEventConsumer: + production: # Instance name, will be included in logs + clientId: your-client-id # (Required) Client ID used by Backstage to identify when connecting to the Kafka cluster. + brokers: # (Required) List of brokers in the Kafka cluster to connect to. + - broker1 + - broker2 + topics: + - topic: 'catalog.entity.created' # (Required) The Backstage topic to consume from + kafka: + topic: kafka-topic-name # (Required) The Kafka topic to publish to ``` For a complete list of all available fields that can be configured, refer to the [config.d.ts file](./config.d.ts). ### Optional SSL Configuration -If your Kafka cluster requires SSL, you can configure it as follows: +If your Kafka cluster requires SSL, you can configure it for both `kafkaConsumingEventPublisher` and `kafkaPublishingEventConsumer` instances: ```yaml events: modules: kafka: kafkaConsumingEventPublisher: - ssl: - rejectUnauthorized: true # (Optional) If true, the server certificate is verified against the list of supplied CAs. - ca: [path/to/ca-cert] # (Optional) Array of trusted certificates in PEM format. - key: path/to/client-key # (Optional) Private key in PEM format. - cert: path/to/client-cert # (Optional) Public x509 certificate in PEM format. + production: + # ... other configuration ... + ssl: + rejectUnauthorized: true # (Optional) If true, the server certificate is verified against the list of supplied CAs. + ca: [path/to/ca-cert] # (Optional) Array of trusted certificates in PEM format. + key: path/to/client-key # (Optional) Private key in PEM format. + cert: path/to/client-cert # (Optional) Public x509 certificate in PEM format. + kafkaPublishingEventConsumer: + production: + # ... other configuration ... + ssl: + # Same SSL configuration options as above ``` ### Optional SASL Authentication Configuration -If your Kafka cluster requires `SASL` authentication, you can configure it as follows: +If your Kafka cluster requires SASL authentication, you can configure it for both components: ```yaml events: modules: kafka: kafkaConsumingEventPublisher: - sasl: - mechanism: 'plain' # SASL mechanism ('plain', 'scram-sha-256' or 'scram-sha-512') - username: your-username # SASL username - password: your-password # SASL password + production: + # ... other configuration ... + sasl: + mechanism: 'plain' # SASL mechanism ('plain', 'scram-sha-256' or 'scram-sha-512') + username: your-username # SASL username + password: your-password # SASL password + kafkaPublishingEventConsumer: + production: + # ... other configuration ... + sasl: + # Same SASL configuration options as above ``` -This section includes optional `SSL` and `SASL` authentication configuration for enhanced security. +These SSL and SASL configurations apply to both Kafka components and provide enhanced security for your Kafka connections. ## Installation diff --git a/plugins/events-backend-module-kafka/config.d.ts b/plugins/events-backend-module-kafka/config.d.ts index 49c417098c..47ebae0653 100644 --- a/plugins/events-backend-module-kafka/config.d.ts +++ b/plugins/events-backend-module-kafka/config.d.ts @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import { HumanDuration } from '@backstage/types'; export interface Config { @@ -25,179 +24,548 @@ export interface Config { kafka?: { /** * Configuration for KafkaConsumingEventPublisher + * + * Supports either: + * 1. Single configuration object (legacy format) + * 2. Multiple named instances as a record where each key is a unique name for the Kafka instance */ - kafkaConsumingEventPublisher?: { - /** - * (Required) Client ID used by Backstage to identify when connecting to the Kafka cluster. - */ - clientId: string; - /** - * (Required) List of brokers in the Kafka cluster to connect to. - */ - brokers: string[]; - /** - * Optional SSL connection parameters to connect to the cluster. Passed directly to Node tls.connect. - * See https://nodejs.org/dist/latest-v8.x/docs/api/tls.html#tls_tls_createsecurecontext_options - */ - ssl?: - | { - ca?: string[]; + kafkaConsumingEventPublisher?: + | { + /** + * (Required) Client ID used by Backstage to identify when connecting to the Kafka cluster. + */ + clientId: string; + /** + * (Required) List of brokers in the Kafka cluster to connect to. + */ + brokers: string[]; + /** + * Optional SSL connection parameters to connect to the cluster. Passed directly to Node tls.connect. + * See https://nodejs.org/dist/latest-v8.x/docs/api/tls.html#tls_tls_createsecurecontext_options + */ + ssl?: + | { + ca?: string[]; + /** @visibility secret */ + key?: string; + cert?: string; + rejectUnauthorized?: boolean; + } + | boolean; + /** + * Optional SASL connection parameters. + */ + sasl?: { + mechanism: 'plain' | 'scram-sha-256' | 'scram-sha-512'; + username: string; /** @visibility secret */ - key?: string; - cert?: string; - rejectUnauthorized?: boolean; - } - | boolean; - /** - * Optional SASL connection parameters. - */ - sasl?: { - mechanism: 'plain' | 'scram-sha-256' | 'scram-sha-512'; - username: string; - /** @visibility secret */ - password: string; - }; + password: string; + }; - /** - * Optional retry connection parameters. - */ - retry: { - /** - * (Optional) Maximum wait time for a retry - * Default: 30000 ms. - */ - maxRetryTime: HumanDuration | string; - - /** - * (Optional) Initial value used to calculate the retry (This is still randomized following the randomization factor) - * Default: 300 ms. - */ - initialRetryTime: HumanDuration | string; - - /** - * (Optional) Randomization factor - * Default: 0.2. - */ - factor: number; - - /** - * (Optional) Exponential factor - * Default: 2. - */ - multiplier: number; - - /** - * (Optional) Max number of retries per call - * Default: 5. - */ - retries: number; - }; - - /** - * (Optional) Timeout for authentication requests. - * Default: 10000 ms. - */ - authenticationTimeout: HumanDuration | string; - - /** - * (Optional) Time to wait for a successful connection. - * Default: 1000 ms. - */ - connectionTimeout: HumanDuration | string; - - /** - * (Optional) Time to wait for a successful request. - * Default: 30000 ms. - */ - requestTimeout: HumanDuration | string; - - /** - * (Optional) The request timeout can be disabled by setting enforceRequestTimeout to false. - * Default: true - */ - enforceRequestTimeout: boolean; - - /** - * Contains a object per topic for which an Kafka queue - * should be used as source of events. - */ - topics: Array<{ - /** - * (Required) The Backstage topic to publish to - */ - topic: string; - /** - * (Required) KafkaConsumer-related configuration. - */ - kafka: { /** - * (Required) The Kafka topics to subscribe to + * Optional retry connection parameters. */ - topics: string[]; - /** - * (Required) The GroupId to be used by the topic consumers - */ - groupId: string; + retry?: { + /** + * (Optional) Maximum wait time for a retry + * Default: 30000 ms. + */ + maxRetryTime?: HumanDuration | string; + + /** + * (Optional) Initial value used to calculate the retry (This is still randomized following the randomization factor) + * Default: 300 ms. + */ + initialRetryTime?: HumanDuration | string; + + /** + * (Optional) Randomization factor + * Default: 0.2. + */ + factor?: number; + + /** + * (Optional) Exponential factor + * Default: 2. + */ + multiplier?: number; + + /** + * (Optional) Max number of retries per call + * Default: 5. + */ + retries?: number; + }; /** - * (Optional) Timeout used to detect failures. - * The consumer sends periodic heartbeats to indicate its liveness to the broker. - * If no heartbeats are received by the broker before the expiration of this session timeout, - * then the broker will remove this consumer from the group and initiate a rebalance + * (Optional) Timeout for authentication requests. + * Default: 10000 ms. + */ + authenticationTimeout?: HumanDuration | string; + + /** + * (Optional) Time to wait for a successful connection. + * Default: 1000 ms. + */ + connectionTimeout?: HumanDuration | string; + + /** + * (Optional) Time to wait for a successful request. * Default: 30000 ms. */ - sessionTimeout: HumanDuration | string; + requestTimeout?: HumanDuration | string; /** - * (Optional) The maximum time that the coordinator will wait for each member to rejoin when rebalancing the group - * Default: 60000 ms. + * (Optional) The request timeout can be disabled by setting enforceRequestTimeout to false. + * Default: true */ - rebalanceTimeout: HumanDuration | string; + enforceRequestTimeout?: boolean; /** - * (Optional) The expected time between heartbeats to the consumer coordinator. - * Heartbeats are used to ensure that the consumer's session stays active. - * The value must be set lower than session timeout - * Default: 3000 ms. + * Contains an object per topic for which a Kafka queue + * should be used as source of events. */ - heartbeatInterval: HumanDuration | string; + topics: Array<{ + /** + * (Required) The Backstage topic to publish to + */ + topic: string; + /** + * (Required) KafkaConsumer-related configuration. + */ + kafka: { + /** + * (Required) The Kafka topics to subscribe to + */ + topics: string[]; + /** + * (Required) The GroupId to be used by the topic consumers + */ + groupId: string; - /** - * (Optional) The period of time after which we force a refresh of metadata - * even if we haven't seen any partition leadership changes to proactively discover any new brokers or partitions - * Default: 300000 ms (5 minutes). - */ - metadataMaxAge: HumanDuration | string; + /** + * (Optional) Timeout used to detect failures. + * The consumer sends periodic heartbeats to indicate its liveness to the broker. + * If no heartbeats are received by the broker before the expiration of this session timeout, + * then the broker will remove this consumer from the group and initiate a rebalance + * Default: 30000 ms. + */ + sessionTimeout?: HumanDuration | string; - /** - * (Optional) The maximum amount of data per-partition the server will return. - * This size must be at least as large as the maximum message size the server allows - * or else it is possible for the producer to send messages larger than the consumer can fetch. - * If that happens, the consumer can get stuck trying to fetch a large message on a certain partition - * Default: 1048576 (1MB) - */ - maxBytesPerPartition: number; + /** + * (Optional) The maximum time that the coordinator will wait for each member to rejoin when rebalancing the group + * Default: 60000 ms. + */ + rebalanceTimeout?: HumanDuration | string; - /** - * (Optional) Minimum amount of data the server should return for a fetch request, otherwise wait up to maxWaitTime for more data to accumulate. - * Default: 1 - */ - minBytes: number; + /** + * (Optional) The expected time between heartbeats to the consumer coordinator. + * Heartbeats are used to ensure that the consumer's session stays active. + * The value must be set lower than session timeout + * Default: 3000 ms. + */ + heartbeatInterval?: HumanDuration | string; - /** - * (Optional) Maximum amount of bytes to accumulate in the response. Supported by Kafka >= 0.10.1.0 - * Default: 10485760 (10MB) - */ - maxBytes: number; + /** + * (Optional) The period of time after which we force a refresh of metadata + * even if we haven't seen any partition leadership changes to proactively discover any new brokers or partitions + * Default: 300000 ms (5 minutes). + */ + metadataMaxAge?: HumanDuration | string; - /** - * (Optional) The maximum amount of time the server will block before answering the fetch request - * if there isn’t sufficient data to immediately satisfy the requirement given by minBytes - * Default: 5000 - */ - maxWaitTime: HumanDuration | string; + /** + * (Optional) The maximum amount of data per-partition the server will return. + * This size must be at least as large as the maximum message size the server allows + * or else it is possible for the producer to send messages larger than the consumer can fetch. + * If that happens, the consumer can get stuck trying to fetch a large message on a certain partition + * Default: 1048576 (1MB) + */ + maxBytesPerPartition?: number; + + /** + * (Optional) Minimum amount of data the server should return for a fetch request, otherwise wait up to maxWaitTime for more data to accumulate. + * Default: 1 + */ + minBytes?: number; + + /** + * (Optional) Maximum amount of bytes to accumulate in the response. Supported by Kafka >= 0.10.1.0 + * Default: 10485760 (10MB) + */ + maxBytes?: number; + + /** + * (Optional) The maximum amount of time the server will block before answering the fetch request + * if there isn't sufficient data to immediately satisfy the requirement given by minBytes + * Default: 5000 + */ + maxWaitTime?: HumanDuration | string; + }; + }>; + } + | { + [name: string]: { + /** + * (Required) Client ID used by Backstage to identify when connecting to the Kafka cluster. + */ + clientId: string; + /** + * (Required) List of brokers in the Kafka cluster to connect to. + */ + brokers: string[]; + /** + * Optional SSL connection parameters to connect to the cluster. Passed directly to Node tls.connect. + * See https://nodejs.org/dist/latest-v8.x/docs/api/tls.html#tls_tls_createsecurecontext_options + */ + ssl?: + | { + ca?: string[]; + /** @visibility secret */ + key?: string; + cert?: string; + rejectUnauthorized?: boolean; + } + | boolean; + /** + * Optional SASL connection parameters. + */ + sasl?: { + mechanism: 'plain' | 'scram-sha-256' | 'scram-sha-512'; + username: string; + /** @visibility secret */ + password: string; + }; + + /** + * Optional retry connection parameters. + */ + retry?: { + /** + * (Optional) Maximum wait time for a retry + * Default: 30000 ms. + */ + maxRetryTime?: HumanDuration | string; + + /** + * (Optional) Initial value used to calculate the retry (This is still randomized following the randomization factor) + * Default: 300 ms. + */ + initialRetryTime?: HumanDuration | string; + + /** + * (Optional) Randomization factor + * Default: 0.2. + */ + factor?: number; + + /** + * (Optional) Exponential factor + * Default: 2. + */ + multiplier?: number; + + /** + * (Optional) Max number of retries per call + * Default: 5. + */ + retries?: number; + }; + + /** + * (Optional) Timeout for authentication requests. + * Default: 10000 ms. + */ + authenticationTimeout?: HumanDuration | string; + + /** + * (Optional) Time to wait for a successful connection. + * Default: 1000 ms. + */ + connectionTimeout?: HumanDuration | string; + + /** + * (Optional) Time to wait for a successful request. + * Default: 30000 ms. + */ + requestTimeout?: HumanDuration | string; + + /** + * (Optional) The request timeout can be disabled by setting enforceRequestTimeout to false. + * Default: true + */ + enforceRequestTimeout?: boolean; + + /** + * Contains an object per topic for which a Kafka queue + * should be used as source of events. + */ + topics: Array<{ + /** + * (Required) The Backstage topic to publish to + */ + topic: string; + /** + * (Required) KafkaConsumer-related configuration. + */ + kafka: { + /** + * (Required) The Kafka topics to subscribe to + */ + topics: string[]; + /** + * (Required) The GroupId to be used by the topic consumers + */ + groupId: string; + + /** + * (Optional) Timeout used to detect failures. + * The consumer sends periodic heartbeats to indicate its liveness to the broker. + * If no heartbeats are received by the broker before the expiration of this session timeout, + * then the broker will remove this consumer from the group and initiate a rebalance + * Default: 30000 ms. + */ + sessionTimeout?: HumanDuration | string; + + /** + * (Optional) The maximum time that the coordinator will wait for each member to rejoin when rebalancing the group + * Default: 60000 ms. + */ + rebalanceTimeout?: HumanDuration | string; + + /** + * (Optional) The expected time between heartbeats to the consumer coordinator. + * Heartbeats are used to ensure that the consumer's session stays active. + * The value must be set lower than session timeout + * Default: 3000 ms. + */ + heartbeatInterval?: HumanDuration | string; + + /** + * (Optional) The period of time after which we force a refresh of metadata + * even if we haven't seen any partition leadership changes to proactively discover any new brokers or partitions + * Default: 300000 ms (5 minutes). + */ + metadataMaxAge?: HumanDuration | string; + + /** + * (Optional) The maximum amount of data per-partition the server will return. + * This size must be at least as large as the maximum message size the server allows + * or else it is possible for the producer to send messages larger than the consumer can fetch. + * If that happens, the consumer can get stuck trying to fetch a large message on a certain partition + * Default: 1048576 (1MB) + */ + maxBytesPerPartition?: number; + + /** + * (Optional) Minimum amount of data the server should return for a fetch request, otherwise wait up to maxWaitTime for more data to accumulate. + * Default: 1 + */ + minBytes?: number; + + /** + * (Optional) Maximum amount of bytes to accumulate in the response. Supported by Kafka >= 0.10.1.0 + * Default: 10485760 (10MB) + */ + maxBytes?: number; + + /** + * (Optional) The maximum amount of time the server will block before answering the fetch request + * if there isn't sufficient data to immediately satisfy the requirement given by minBytes + * Default: 5000 + */ + maxWaitTime?: HumanDuration | string; + }; + }>; + }; }; - }>; + + /** + * Configuration for KafkaPublishingEventConsumer + * + * Supports multiple named instances as a record where each key is a unique name + * for the Kafka producer configuration. + */ + kafkaPublishingEventConsumer?: { + [name: string]: { + /** + * (Required) Client ID used by Backstage to identify when connecting to the Kafka cluster. + */ + clientId: string; + /** + * (Required) List of brokers in the Kafka cluster to connect to. + */ + brokers: string[]; + /** + * Optional SSL connection parameters to connect to the cluster. Passed directly to Node tls.connect. + * See https://nodejs.org/dist/latest-v8.x/docs/api/tls.html#tls_tls_createsecurecontext_options + */ + ssl?: + | { + ca?: string[]; + /** @visibility secret */ + key?: string; + cert?: string; + rejectUnauthorized?: boolean; + } + | boolean; + /** + * Optional SASL connection parameters. + */ + sasl?: { + mechanism: 'plain' | 'scram-sha-256' | 'scram-sha-512'; + username: string; + /** @visibility secret */ + password: string; + }; + + /** + * Optional retry connection parameters. + */ + retry?: { + /** + * (Optional) Maximum wait time for a retry + * Default: 30000 ms. + */ + maxRetryTime?: HumanDuration | string; + + /** + * (Optional) Initial value used to calculate the retry (This is still randomized following the randomization factor) + * Default: 300 ms. + */ + initialRetryTime?: HumanDuration | string; + + /** + * (Optional) Randomization factor + * Default: 0.2. + */ + factor?: number; + + /** + * (Optional) Exponential factor + * Default: 2. + */ + multiplier?: number; + + /** + * (Optional) Max number of retries per call + * Default: 5. + */ + retries?: number; + }; + + /** + * (Optional) Timeout for authentication requests. + * Default: 10000 ms. + */ + authenticationTimeout?: HumanDuration | string; + + /** + * (Optional) Time to wait for a successful connection. + * Default: 1000 ms. + */ + connectionTimeout?: HumanDuration | string; + + /** + * (Optional) Time to wait for a successful request. + * Default: 30000 ms. + */ + requestTimeout?: HumanDuration | string; + + /** + * (Optional) The request timeout can be disabled by setting enforceRequestTimeout to false. + * Default: true + */ + enforceRequestTimeout?: boolean; + + /** + * Contains an object per topic for which a Kafka queue + * should be used as destination for events. + */ + topics: Array<{ + /** + * (Required) The Backstage topic to consume from + */ + topic: string; + /** + * (Required) KafkaProducer-related configuration. + */ + kafka: { + /** + * (Required) The Kafka topic to publish to + */ + topic: string; + + /** + * (Optional) Allow topic creation when querying metadata for non-existent topics. + * Default: true + */ + allowAutoTopicCreation?: boolean; + + /** + * (Optional) The period of time after which we force a refresh of metadata + * even if we haven't seen any partition leadership changes to proactively discover any new brokers or partitions + * Default: 300000 ms (5 minutes). + */ + metadataMaxAge?: HumanDuration | string; + + /** + * (Optional) The maximum amount of time in ms that the transaction coordinator will wait for a transaction status update + * from the producer before proactively aborting the ongoing transaction. + * If this value is larger than the `transaction.max.timeout.ms`` setting in the broker, the request will fail with a `InvalidTransactionTimeout` error + * Default: 60000 ms. + */ + transactionTimeout?: HumanDuration | string; + + /** + * (Optional) Experimental. If enabled producer will ensure each message is written exactly once. Acks must be set to -1 ("all"). + * Retries will default to MAX_SAFE_INTEGER. + * Default: false. + */ + idempotent?: boolean; + + /** + * (Optional) Max number of requests that may be in progress at any time. If falsey then no limit. + * Default: null. + */ + maxInFlightRequests?: number; + + /** + * Optional retry connection parameters. + */ + retry?: { + /** + * (Optional) Maximum wait time for a retry + * Default: 30000 ms. + */ + maxRetryTime?: HumanDuration | string; + + /** + * (Optional) Initial value used to calculate the retry (This is still randomized following the randomization factor) + * Default: 300 ms. + */ + initialRetryTime?: HumanDuration | string; + + /** + * (Optional) Randomization factor + * Default: 0.2. + */ + factor?: number; + + /** + * (Optional) Exponential factor + * Default: 2. + */ + multiplier?: number; + + /** + * (Optional) Max number of retries per call + * Default: 5. + */ + retries?: number; + }; + }; + }>; + }; }; }; }; diff --git a/plugins/events-backend-module-kafka/package.json b/plugins/events-backend-module-kafka/package.json index 93d624a97c..d6e7b1ac0e 100644 --- a/plugins/events-backend-module-kafka/package.json +++ b/plugins/events-backend-module-kafka/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-kafka", - "version": "0.1.5", + "version": "0.2.0", "description": "The kafka backend module for the events plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/events-backend-module-kafka/report.api.md b/plugins/events-backend-module-kafka/report.api.md index a6b129b82e..c5e6d29c6a 100644 --- a/plugins/events-backend-module-kafka/report.api.md +++ b/plugins/events-backend-module-kafka/report.api.md @@ -6,6 +6,6 @@ import { BackendFeature } from '@backstage/backend-plugin-api'; // @public -const eventsModuleKafkaConsumingEventPublisher: BackendFeature; -export default eventsModuleKafkaConsumingEventPublisher; +const _default: BackendFeature; +export default _default; ``` diff --git a/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/KafkaConsumingEventPublisher.test.ts b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/KafkaConsumingEventPublisher.test.ts new file mode 100644 index 0000000000..98781936b0 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/KafkaConsumingEventPublisher.test.ts @@ -0,0 +1,163 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { KafkaConsumingEventPublisher } from './KafkaConsumingEventPublisher'; +import { Kafka } from 'kafkajs'; +import { ConfigReader } from '@backstage/config'; +import { mockServices } from '@backstage/backend-test-utils'; + +jest.mock('kafkajs'); + +describe('KafkaConsumingEventPublisher', () => { + const mockLogger = mockServices.logger.mock(); + const mockEvents = mockServices.events.mock(); + + const mockConsumer = { + connect: jest.fn(), + disconnect: jest.fn(), + subscribe: jest.fn(), + run: jest.fn(), + }; + + const mockKafkaClient = { + consumer: jest.fn().mockReturnValue(mockConsumer), + } as unknown as Kafka; + + jest.mocked(Kafka).mockImplementation(() => mockKafkaClient); + + const mockConfig = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaConsumingEventPublisher: { + dev: { + clientId: 'backstage-events', + brokers: ['kafka1:9092', 'kafka2:9092'], + topics: [ + { + topic: 'backstage-topic', + kafka: { + topics: ['test-topic'], + groupId: 'test-group', + }, + }, + ], + }, + }, + }, + }, + }, + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should create instances from config', () => { + const consumers = KafkaConsumingEventPublisher.fromConfig({ + config: mockConfig, + events: mockEvents, + logger: mockLogger, + }); + + expect(consumers).toBeInstanceOf(Array); + expect(consumers).toHaveLength(1); + expect(consumers[0]).toBeInstanceOf(KafkaConsumingEventPublisher); + }); + + it('should return empty array when no config', () => { + const consumers = KafkaConsumingEventPublisher.fromConfig({ + config: new ConfigReader({}), + events: mockEvents, + logger: mockLogger, + }); + + expect(consumers).toEqual([]); + }); + + it('should start all consumers', async () => { + const consumers = KafkaConsumingEventPublisher.fromConfig({ + config: mockConfig, + events: mockEvents, + logger: mockLogger, + }); + + expect(consumers).toHaveLength(1); + + await consumers[0].start(); + + expect(mockConsumer.connect).toHaveBeenCalled(); + expect(mockConsumer.subscribe).toHaveBeenCalledWith({ + topics: ['test-topic'], + }); + expect(mockConsumer.run).toHaveBeenCalled(); + }); + + it('should shutdown all consumers', async () => { + const consumers = KafkaConsumingEventPublisher.fromConfig({ + config: mockConfig, + events: mockEvents, + logger: mockLogger, + }); + + expect(consumers).toHaveLength(1); + + await consumers[0].shutdown(); + + expect(mockConsumer.disconnect).toHaveBeenCalled(); + }); + + it('should handle multiple consumer configs', () => { + const multiConsumerConfig = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaConsumingEventPublisher: { + dev: { + clientId: 'backstage-events', + brokers: ['kafka1:9092'], + topics: [ + { + topic: 'topic1', + kafka: { + topics: ['kafka-topic-1'], + groupId: 'group1', + }, + }, + { + topic: 'topic2', + kafka: { + topics: ['kafka-topic-2'], + groupId: 'group2', + }, + }, + ], + }, + }, + }, + }, + }, + }); + + const consumers = KafkaConsumingEventPublisher.fromConfig({ + config: multiConsumerConfig, + events: mockEvents, + logger: mockLogger, + }); + + expect(consumers).toHaveLength(1); + expect(mockKafkaClient.consumer).toHaveBeenCalledTimes(2); + }); +}); diff --git a/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/KafkaConsumingEventPublisher.ts b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/KafkaConsumingEventPublisher.ts new file mode 100644 index 0000000000..b996032575 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/KafkaConsumingEventPublisher.ts @@ -0,0 +1,109 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { LoggerService } from '@backstage/backend-plugin-api'; +import { EventsService } from '@backstage/plugin-events-node'; +import { Consumer, Kafka } from 'kafkajs'; +import { + KafkaConsumerConfig, + KafkaConsumingEventPublisherConfig, + readConsumerConfig, +} from './config'; +import { Config } from '@backstage/config'; +import { loggerServiceAdapter } from '../utils/LoggerServiceAdapter'; +import { convertHeadersToMetadata } from '../utils/kafkaTransformers'; + +type KafkaConsumer = { + consumer: Consumer; + config: KafkaConsumerConfig; +}; + +/** + * This class subscribes to Kafka topics and publishes events received to the registered subscriber. + * The message payload will be used as the event payload and passed to the subscribers. + */ +export class KafkaConsumingEventPublisher { + private readonly kafkaConsumers: KafkaConsumer[]; + private readonly logger: LoggerService; + + static fromConfig(env: { + config: Config; + events: EventsService; + logger: LoggerService; + }): KafkaConsumingEventPublisher[] { + const configs = readConsumerConfig(env.config, env.logger); + + return configs.map( + kafkaConfig => + new KafkaConsumingEventPublisher(env.logger, env.events, kafkaConfig), + ); + } + + private constructor( + logger: LoggerService, + private readonly events: EventsService, + config: KafkaConsumingEventPublisherConfig, + ) { + this.logger = logger.child({ + class: KafkaConsumingEventPublisher.prototype.constructor.name, + instance: config.instance, + }); + + const kafka = new Kafka({ + ...config.kafkaConfig, + logCreator: loggerServiceAdapter(this.logger), + }); + + this.kafkaConsumers = config.kafkaConsumerConfigs.map(consumerConfig => ({ + consumer: kafka.consumer(consumerConfig.consumerConfig), + config: consumerConfig, + })); + } + + async start(): Promise { + await Promise.all( + this.kafkaConsumers.map(async ({ consumer, config }) => { + const consumerLogger = this.logger.child({ + id: `events.kafka.publisher:${config.backstageTopic}`, + groupId: config.consumerConfig.groupId, + kafkaTopics: config.consumerSubscribeTopics.topics.toString(), + backstageTopic: config.backstageTopic, + }); + try { + await consumer.connect(); + await consumer.subscribe(config.consumerSubscribeTopics); + + await consumer.run({ + eachMessage: async ({ message }) => { + this.events.publish({ + topic: config.backstageTopic, + eventPayload: JSON.parse(message.value?.toString()!), + metadata: convertHeadersToMetadata(message.headers), + }); + }, + }); + } catch (error: any) { + consumerLogger.error('Kafka consumer connection failed', error); + } + }), + ); + } + + async shutdown(): Promise { + await Promise.all( + this.kafkaConsumers.map(({ consumer }) => consumer.disconnect()), + ); + } +} diff --git a/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/config.test.ts b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/config.test.ts new file mode 100644 index 0000000000..16ee9e0a4c --- /dev/null +++ b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/config.test.ts @@ -0,0 +1,354 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ConfigReader } from '@backstage/config'; +import { readConsumerConfig } from './config'; +import { mockServices } from '@backstage/backend-test-utils'; + +const mockLogger = mockServices.logger.mock(); + +describe('readConsumerConfig', () => { + it('not configured', () => { + const publisherConfigs = readConsumerConfig( + new ConfigReader({}), + mockLogger, + ); + + expect(publisherConfigs).toEqual([]); + }); + + it('only required fields configured', () => { + const config = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaConsumingEventPublisher: { + dev: { + clientId: 'backstage-events', + brokers: ['kafka1:9092', 'kafka2:9092'], + topics: [ + { + topic: 'fake1', + kafka: { + topics: ['topic-A'], + groupId: 'my-group', + }, + }, + { + topic: 'fake2', + kafka: { + topics: ['topic-B'], + groupId: 'my-group', + }, + }, + ], + }, + }, + }, + }, + }, + }); + + const publisherConfigs = readConsumerConfig(config, mockLogger); + + expect(publisherConfigs).toBeDefined(); + expect(Array.isArray(publisherConfigs)).toBe(true); + expect(publisherConfigs).toHaveLength(1); + + const devConfig = publisherConfigs[0]; + expect(devConfig.instance).toBe('dev'); + expect(devConfig.kafkaConsumerConfigs.length).toBe(2); + + expect(devConfig.kafkaConfig.clientId).toEqual('backstage-events'); + expect(devConfig.kafkaConfig.brokers).toEqual([ + 'kafka1:9092', + 'kafka2:9092', + ]); + + expect(devConfig.kafkaConsumerConfigs).toEqual([ + { + backstageTopic: 'fake1', + consumerConfig: { + groupId: 'my-group', + }, + consumerSubscribeTopics: { + topics: ['topic-A'], + }, + }, + { + backstageTopic: 'fake2', + consumerConfig: { + groupId: 'my-group', + }, + consumerSubscribeTopics: { + topics: ['topic-B'], + }, + }, + ]); + }); + + it('all fields configured', () => { + const config = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaConsumingEventPublisher: { + dev: { + clientId: 'backstage-events', + brokers: ['kafka1:9092', 'kafka2:9092'], + ssl: true, + sasl: { + mechanism: 'plain', + username: 'username', + password: 'password', + }, + retry: { + maxRetryTime: { milliseconds: 20000 }, + initialRetryTime: { milliseconds: 200 }, + factor: '0.4', + multiplier: '4', + retries: '10', + }, + authenticationTimeout: { milliseconds: 20000 }, + connectionTimeout: { milliseconds: 1500 }, + requestTimeout: { milliseconds: 20000 }, + enforceRequestTimeout: false, + topics: [ + { + topic: 'fake1', + kafka: { + topics: ['topic-A'], + groupId: 'my-group', + sessionTimeout: { milliseconds: 20000 }, + rebalanceTimeout: { milliseconds: 50000 }, + heartbeatInterval: { milliseconds: 2000 }, + metadataMaxAge: { milliseconds: 400000 }, + maxBytesPerPartition: 50000, + minBytes: 2, + maxBytes: 500000, + maxWaitTime: { milliseconds: 4000 }, + }, + }, + { + topic: 'fake2', + kafka: { + topics: ['topic-B'], + groupId: 'my-group', + }, + }, + ], + }, + }, + }, + }, + }, + }); + + const publisherConfigs = readConsumerConfig(config, mockLogger); + + expect(publisherConfigs).toBeDefined(); + expect(Array.isArray(publisherConfigs)).toBe(true); + expect(publisherConfigs).toHaveLength(1); + + const devConfig = publisherConfigs[0]; + expect(devConfig.instance).toBe('dev'); + + // Client configuration + expect(devConfig.kafkaConfig.clientId).toEqual('backstage-events'); + expect(devConfig.kafkaConfig.brokers).toEqual([ + 'kafka1:9092', + 'kafka2:9092', + ]); + expect(devConfig.kafkaConfig.ssl).toBeTruthy(); + expect(devConfig.kafkaConfig.sasl).toStrictEqual({ + mechanism: 'plain', + username: 'username', + password: 'password', + }); + expect(devConfig.kafkaConfig.authenticationTimeout).toBe(20000); + expect(devConfig.kafkaConfig.connectionTimeout).toBe(1500); + expect(devConfig.kafkaConfig.requestTimeout).toBe(20000); + expect(devConfig.kafkaConfig.enforceRequestTimeout).toBeFalsy(); + expect(devConfig.kafkaConfig.retry).toStrictEqual({ + maxRetryTime: 20000, + initialRetryTime: 200, + factor: 0.4, + multiplier: 4, + retries: 10, + }); + + // Consumer configuration + expect(devConfig.kafkaConsumerConfigs.length).toBe(2); + + expect(devConfig.kafkaConsumerConfigs).toEqual([ + { + backstageTopic: 'fake1', + consumerConfig: { + groupId: 'my-group', + sessionTimeout: 20000, + rebalanceTimeout: 50000, + heartbeatInterval: 2000, + metadataMaxAge: 400000, + maxBytesPerPartition: 50000, + minBytes: 2, + maxBytes: 500000, + maxWaitTimeInMs: 4000, + }, + consumerSubscribeTopics: { + topics: ['topic-A'], + }, + }, + { + backstageTopic: 'fake2', + consumerConfig: { + groupId: 'my-group', + }, + consumerSubscribeTopics: { + topics: ['topic-B'], + }, + }, + ]); + }); + + it('should handle HumanDuration and string values for durations and timeouts', () => { + const config = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaConsumingEventPublisher: { + dev: { + clientId: 'backstage-events', + brokers: ['kafka1:9092', 'kafka2:9092'], + retry: { + maxRetryTime: { seconds: 1 }, + initialRetryTime: { minutes: 1 }, + factor: 0.4, + multiplier: 4, + retries: 10, + }, + authenticationTimeout: { hours: 1 }, + connectionTimeout: { days: 1 }, + topics: [], + requestTimeout: '1m', + }, + }, + }, + }, + }, + }); + + const publisherConfigs = readConsumerConfig(config, mockLogger); + + expect(publisherConfigs).toBeDefined(); + expect(Array.isArray(publisherConfigs)).toBe(true); + expect(publisherConfigs).toHaveLength(1); + + const devConfig = publisherConfigs[0]; + expect(devConfig.instance).toBe('dev'); + + // Client configuration + expect(devConfig.kafkaConfig.clientId).toEqual('backstage-events'); + expect(devConfig.kafkaConfig.brokers).toEqual([ + 'kafka1:9092', + 'kafka2:9092', + ]); + expect(devConfig.kafkaConfig.authenticationTimeout).toBe(3600000); + expect(devConfig.kafkaConfig.connectionTimeout).toBe(86400000); + expect(devConfig.kafkaConfig.requestTimeout).toBe(60000); + expect(devConfig.kafkaConfig.retry).toStrictEqual({ + maxRetryTime: 1000, + initialRetryTime: 60000, + factor: 0.4, + multiplier: 4, + retries: 10, + }); + + // Consumer configuration + expect(devConfig.kafkaConsumerConfigs.length).toBe(0); + }); + + it('single instance configuration (legacy format)', () => { + const config = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaConsumingEventPublisher: { + clientId: 'backstage-events', + brokers: ['kafka1:9092', 'kafka2:9092'], + topics: [ + { + topic: 'fake1', + kafka: { + topics: ['topic-A'], + groupId: 'my-group', + }, + }, + { + topic: 'fake2', + kafka: { + topics: ['topic-B'], + groupId: 'my-group', + }, + }, + ], + }, + }, + }, + }, + }); + + const publisherConfigs = readConsumerConfig(config, mockLogger); + + expect(publisherConfigs).toBeDefined(); + expect(Array.isArray(publisherConfigs)).toBe(true); + expect(publisherConfigs).toHaveLength(1); + + const defaultConfig = publisherConfigs[0]; + expect(defaultConfig.instance).toBe('default'); + expect(defaultConfig.kafkaConsumerConfigs.length).toBe(2); + + expect(defaultConfig.kafkaConfig.clientId).toEqual('backstage-events'); + expect(defaultConfig.kafkaConfig.brokers).toEqual([ + 'kafka1:9092', + 'kafka2:9092', + ]); + + expect(defaultConfig.kafkaConsumerConfigs).toEqual([ + { + backstageTopic: 'fake1', + consumerConfig: { + groupId: 'my-group', + }, + consumerSubscribeTopics: { + topics: ['topic-A'], + }, + }, + { + backstageTopic: 'fake2', + consumerConfig: { + groupId: 'my-group', + }, + consumerSubscribeTopics: { + topics: ['topic-B'], + }, + }, + ]); + + // Verify deprecation warning was logged + expect(mockLogger.warn).toHaveBeenCalledWith( + 'Legacy single config format detected at events.modules.kafka.kafkaConsumingEventPublisher.', + ); + }); +}); diff --git a/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/config.ts b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/config.ts new file mode 100644 index 0000000000..b07378a1d6 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/config.ts @@ -0,0 +1,116 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Config } from '@backstage/config'; +import { ConsumerConfig, ConsumerSubscribeTopics, KafkaConfig } from 'kafkajs'; +import { + readKafkaConfig, + readOptionalHumanDurationInMs, +} from '../utils/config'; +import { LoggerService } from '@backstage/backend-plugin-api'; + +export interface KafkaConsumerConfig { + backstageTopic: string; + consumerConfig: ConsumerConfig; + consumerSubscribeTopics: ConsumerSubscribeTopics; +} + +export interface KafkaConsumingEventPublisherConfig { + instance: string; + kafkaConfig: KafkaConfig; + kafkaConsumerConfigs: KafkaConsumerConfig[]; +} + +const CONFIG_PREFIX_PUBLISHER = + 'events.modules.kafka.kafkaConsumingEventPublisher'; + +const processSinglePublisher = ( + instanceName: string, + publisherConfig: Config, +): KafkaConsumingEventPublisherConfig => { + return { + instance: instanceName, + kafkaConfig: readKafkaConfig(publisherConfig), + kafkaConsumerConfigs: publisherConfig + .getConfigArray('topics') + .map(topicConfig => { + return { + backstageTopic: topicConfig.getString('topic'), + consumerConfig: { + groupId: topicConfig.getString('kafka.groupId'), + sessionTimeout: readOptionalHumanDurationInMs( + topicConfig, + 'kafka.sessionTimeout', + ), + rebalanceTimeout: readOptionalHumanDurationInMs( + topicConfig, + 'kafka.rebalanceTimeout', + ), + heartbeatInterval: readOptionalHumanDurationInMs( + topicConfig, + 'kafka.heartbeatInterval', + ), + metadataMaxAge: readOptionalHumanDurationInMs( + topicConfig, + 'kafka.metadataMaxAge', + ), + maxBytesPerPartition: topicConfig.getOptionalNumber( + 'kafka.maxBytesPerPartition', + ), + minBytes: topicConfig.getOptionalNumber('kafka.minBytes'), + maxBytes: topicConfig.getOptionalNumber('kafka.maxBytes'), + maxWaitTimeInMs: readOptionalHumanDurationInMs( + topicConfig, + 'kafka.maxWaitTime', + ), + }, + consumerSubscribeTopics: { + topics: topicConfig.getStringArray('kafka.topics'), + }, + }; + }), + }; +}; + +export const readConsumerConfig = ( + config: Config, + logger: LoggerService, +): KafkaConsumingEventPublisherConfig[] => { + const publishersConfig = config.getOptionalConfig(CONFIG_PREFIX_PUBLISHER); + + // Check for legacy single publisher format + if (publishersConfig?.getOptionalString('clientId')) { + logger.warn( + 'Legacy single config format detected at events.modules.kafka.kafkaConsumingEventPublisher.', + ); + return [ + processSinglePublisher( + 'default', // use `default` as instance name for legacy single config + publishersConfig, + ), + ]; + } + + return ( + publishersConfig + ?.keys() + ?.map(publisherKey => + processSinglePublisher( + publisherKey, + publishersConfig.getConfig(publisherKey), + ), + ) ?? [] + ); +}; diff --git a/packages/backend-defaults/src/alpha/entrypoints/instanceMetadata/index.ts b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/index.ts similarity index 87% rename from packages/backend-defaults/src/alpha/entrypoints/instanceMetadata/index.ts rename to plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/index.ts index 00ee75400a..94d36a81e0 100644 --- a/packages/backend-defaults/src/alpha/entrypoints/instanceMetadata/index.ts +++ b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/index.ts @@ -13,5 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -export { instanceMetadataServiceFactory } from './instanceMetadataServiceFactory'; +export { eventsModuleKafkaConsumingEventPublisher } from './module'; diff --git a/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/module.test.ts b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/module.test.ts new file mode 100644 index 0000000000..19ac969800 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/module.test.ts @@ -0,0 +1,122 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createServiceFactory } from '@backstage/backend-plugin-api'; +import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; +import { eventsServiceRef } from '@backstage/plugin-events-node'; +import { TestEventsService } from '@backstage/plugin-events-backend-test-utils'; +import { eventsModuleKafkaConsumingEventPublisher } from './module'; +import { KafkaConsumingEventPublisher } from './KafkaConsumingEventPublisher'; + +jest.mock('./KafkaConsumingEventPublisher'); + +describe('eventsModuleKafkaConsumingEventPublisher', () => { + it('should be correctly wired and set up', async () => { + const events = new TestEventsService(); + const eventsServiceFactory = createServiceFactory({ + service: eventsServiceRef, + deps: {}, + async factory({}) { + return events; + }, + }); + + const mockKafkaConsumingEventPublisher = { + start: jest.fn(), + shutdown: jest.fn(), + } as unknown as KafkaConsumingEventPublisher; + + jest + .mocked(KafkaConsumingEventPublisher.fromConfig) + .mockReturnValue([mockKafkaConsumingEventPublisher]); + + await startTestBackend({ + features: [ + eventsServiceFactory, + eventsModuleKafkaConsumingEventPublisher, + mockServices.rootConfig.factory({ + data: { + events: { + modules: { + kafka: { + kafkaConsumingEventPublisher: { + dev: { + clientId: 'backstage-events', + brokers: ['kafka1:9092', 'kafka2:9092'], + topics: { + fake1: { + kafka: { + topics: ['topic-A'], + groupId: 'my-group', + }, + }, + fake2: { + kafka: { + topics: ['topic-B'], + groupId: 'my-group', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }), + ], + }); + + // Verify that the Kafka consumer client was started + expect(mockKafkaConsumingEventPublisher.start).toHaveBeenCalled(); + + // Verify that the shutdown hook was registered + expect(mockKafkaConsumingEventPublisher.shutdown).not.toHaveBeenCalled(); + }); + + it('should handle empty configuration gracefully', async () => { + const events = new TestEventsService(); + const eventsServiceFactory = createServiceFactory({ + service: eventsServiceRef, + deps: {}, + async factory({}) { + return events; + }, + }); + + jest.mocked(KafkaConsumingEventPublisher.fromConfig).mockReturnValue([]); + + await startTestBackend({ + features: [ + eventsServiceFactory, + eventsModuleKafkaConsumingEventPublisher, + mockServices.rootConfig.factory({ + data: { + events: { + modules: { + kafka: { + // No kafkaConsumingEventPublisher config + }, + }, + }, + }, + }), + ], + }); + + // Verify that fromConfig was called but returned empty array + expect(KafkaConsumingEventPublisher.fromConfig).toHaveBeenCalled(); + }); +}); diff --git a/plugins/events-backend-module-kafka/src/service/eventsModuleKafkaConsumingEventPublisher.ts b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/module.ts similarity index 69% rename from plugins/events-backend-module-kafka/src/service/eventsModuleKafkaConsumingEventPublisher.ts rename to plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/module.ts index b524454ca0..8ad3396c76 100644 --- a/plugins/events-backend-module-kafka/src/service/eventsModuleKafkaConsumingEventPublisher.ts +++ b/plugins/events-backend-module-kafka/src/KafkaConsumingEventPublisher/module.ts @@ -17,11 +17,11 @@ import { coreServices, createBackendModule, } from '@backstage/backend-plugin-api'; -import { KafkaConsumerClient } from '../publisher/KafkaConsumerClient'; import { eventsServiceRef } from '@backstage/plugin-events-node'; +import { KafkaConsumingEventPublisher } from './KafkaConsumingEventPublisher'; /** - * Kafka module for the Events plugin. + * Reads messages off of Kafka topics and forwards them into the Backstage events system. * * @public */ @@ -34,22 +34,22 @@ export const eventsModuleKafkaConsumingEventPublisher = createBackendModule({ config: coreServices.rootConfig, events: eventsServiceRef, logger: coreServices.logger, - lifecycle: coreServices.lifecycle, + lifecycle: coreServices.rootLifecycle, }, async init({ config, logger, events, lifecycle }) { - const kafka = KafkaConsumerClient.fromConfig({ + const consumers = KafkaConsumingEventPublisher.fromConfig({ config, events, logger, }); - if (!kafka) { - return; - } + lifecycle.addStartupHook(async () => { + await Promise.all(consumers.map(consumer => consumer.start())); + }); - await kafka.start(); - - lifecycle.addShutdownHook(async () => await kafka.shutdown()); + lifecycle.addShutdownHook(async () => { + await Promise.all(consumers.map(consumer => consumer.shutdown())); + }); }, }); }, diff --git a/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/KafkaPublishingEventConsumer.test.ts b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/KafkaPublishingEventConsumer.test.ts new file mode 100644 index 0000000000..6379605901 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/KafkaPublishingEventConsumer.test.ts @@ -0,0 +1,106 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { KafkaPublishingEventConsumer } from './KafkaPublishingEventConsumer'; +import { Kafka } from 'kafkajs'; +import { mockServices } from '@backstage/backend-test-utils'; +import { ConfigReader } from '@backstage/config'; + +jest.mock('kafkajs'); + +describe('KafkaPublishingEventConsumer', () => { + const mockLogger = mockServices.logger.mock(); + const mockEvents = mockServices.events.mock(); + + const mockProducer = { + connect: jest.fn(), + disconnect: jest.fn(), + send: jest.fn(), + }; + + const mockKafkaClient = { + producer: jest.fn().mockReturnValue(mockProducer), + } as unknown as Kafka; + + jest.mocked(Kafka).mockImplementation(() => mockKafkaClient); + + const mockConfig = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaPublishingEventConsumer: { + dev: { + clientId: 'backstage-events', + brokers: ['kafka1:9092'], + topics: [ + { + topic: 'backstage-topic', + kafka: { + topic: 'kafka-topic', + allowAutoTopicCreation: true, + }, + }, + ], + }, + }, + }, + }, + }, + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should create instances from config', () => { + const consumers = KafkaPublishingEventConsumer.fromConfig({ + config: mockConfig, + events: mockEvents, + logger: mockLogger, + }); + + expect(consumers).toHaveLength(1); + expect(consumers[0]).toBeInstanceOf(KafkaPublishingEventConsumer); + }); + + it('should start the consumer and subscribe to events', async () => { + const consumers = KafkaPublishingEventConsumer.fromConfig({ + config: mockConfig, + events: mockEvents, + logger: mockLogger, + }); + + await consumers[0].start(); + + expect(mockProducer.connect).toHaveBeenCalled(); + expect(mockEvents.subscribe).toHaveBeenCalledWith({ + id: 'kafka:publisher:backstage-topic', + topics: ['backstage-topic'], + onEvent: expect.any(Function), + }); + }); + + it('should shutdown the producer', async () => { + const consumers = KafkaPublishingEventConsumer.fromConfig({ + config: mockConfig, + events: mockEvents, + logger: mockLogger, + }); + + await consumers[0].shutdown(); + + expect(mockProducer.disconnect).toHaveBeenCalled(); + }); +}); diff --git a/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/KafkaPublishingEventConsumer.ts b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/KafkaPublishingEventConsumer.ts new file mode 100644 index 0000000000..f9443f2d69 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/KafkaPublishingEventConsumer.ts @@ -0,0 +1,115 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { LoggerService } from '@backstage/backend-plugin-api'; +import { EventParams, EventsService } from '@backstage/plugin-events-node'; +import { Kafka, Producer } from 'kafkajs'; +import { + KafkaPublisherConfig, + KafkaPublishingEventConsumerConfig, + readPublisherConfig, +} from './config'; +import { Config } from '@backstage/config'; +import { loggerServiceAdapter } from '../utils/LoggerServiceAdapter'; +import { payloadToBuffer } from '../utils/kafkaTransformers'; + +type KafkaPublisher = { + producer: Producer; + config: KafkaPublisherConfig; +}; + +/** + * This class subscribes to Backstage internal events and publishes them to Kafka topics. + * The internal event payload will be serialized and sent to the configured Kafka topic. + */ +export class KafkaPublishingEventConsumer { + private readonly kafkaPublishers: KafkaPublisher[]; + private readonly logger: LoggerService; + + static fromConfig(env: { + config: Config; + events: EventsService; + logger: LoggerService; + }): KafkaPublishingEventConsumer[] { + const configs = readPublisherConfig(env.config); + + return configs.map( + kafkaConfig => + new KafkaPublishingEventConsumer(env.logger, env.events, kafkaConfig), + ); + } + + private constructor( + logger: LoggerService, + private readonly events: EventsService, + config: KafkaPublishingEventConsumerConfig, + ) { + this.logger = logger.child({ + class: KafkaPublishingEventConsumer.prototype.constructor.name, + instance: config.instance, + }); + + const kafka = new Kafka({ + ...config.kafkaConfig, + logCreator: loggerServiceAdapter(this.logger), + }); + + this.kafkaPublishers = config.kafkaPublisherConfigs.map( + publisherConfig => ({ + producer: kafka.producer(publisherConfig.producerConfig), + config: publisherConfig, + }), + ); + } + + async start(): Promise { + await Promise.all( + this.kafkaPublishers.map(async ({ producer, config }) => { + try { + await producer.connect(); + + this.events.subscribe({ + id: `kafka:publisher:${config.backstageTopic}`, + topics: [config.backstageTopic], + onEvent: async (params: EventParams) => { + await producer.send({ + topic: config.kafkaTopic, + messages: [ + { + value: payloadToBuffer(params.eventPayload), + }, + ], + }); + }, + }); + this.logger.info( + `Subscribed to EventService, publishing events to external topic: ${config.backstageTopic}`, + ); + } catch (error: any) { + this.logger.error( + `Kafka producer connection failed for topic ${config.backstageTopic}`, + error, + ); + } + }), + ); + } + + async shutdown(): Promise { + await Promise.all( + this.kafkaPublishers.map(({ producer }) => producer.disconnect()), + ); + } +} diff --git a/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/config.test.ts b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/config.test.ts new file mode 100644 index 0000000000..bd4d622238 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/config.test.ts @@ -0,0 +1,360 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ConfigReader } from '@backstage/config'; +import { readPublisherConfig } from './config'; + +describe('readPublisherConfig', () => { + it('not configured', () => { + const publisherConfigs = readPublisherConfig(new ConfigReader({})); + + expect(publisherConfigs).toEqual([]); + }); + + it('only required fields configured', () => { + const config = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaPublishingEventConsumer: { + dev: { + clientId: 'backstage-events', + brokers: ['kafka1:9092', 'kafka2:9092'], + topics: [ + { + topic: 'fake1', + kafka: { + topic: 'topic-A', + }, + }, + { + topic: 'fake2', + kafka: { + topic: 'topic-B', + }, + }, + ], + }, + }, + }, + }, + }, + }); + + const publisherConfigs = readPublisherConfig(config); + + expect(publisherConfigs).toBeDefined(); + expect(Array.isArray(publisherConfigs)).toBe(true); + expect(publisherConfigs).toHaveLength(1); + + const devConfig = publisherConfigs[0]; + expect(devConfig.instance).toBe('dev'); + expect(devConfig.kafkaPublisherConfigs.length).toBe(2); + + expect(devConfig.kafkaConfig.clientId).toEqual('backstage-events'); + expect(devConfig.kafkaConfig.brokers).toEqual([ + 'kafka1:9092', + 'kafka2:9092', + ]); + + expect(devConfig.kafkaPublisherConfigs).toEqual([ + { + backstageTopic: 'fake1', + kafkaTopic: 'topic-A', + producerConfig: { + allowAutoTopicCreation: undefined, + metadataMaxAge: undefined, + transactionTimeout: undefined, + idempotent: undefined, + maxInFlightRequests: undefined, + retry: {}, + }, + }, + { + backstageTopic: 'fake2', + kafkaTopic: 'topic-B', + producerConfig: { + allowAutoTopicCreation: undefined, + metadataMaxAge: undefined, + transactionTimeout: undefined, + idempotent: undefined, + maxInFlightRequests: undefined, + retry: {}, + }, + }, + ]); + }); + + it('all fields configured', () => { + const config = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaPublishingEventConsumer: { + dev: { + clientId: 'backstage-events', + brokers: ['kafka1:9092', 'kafka2:9092'], + ssl: true, + sasl: { + mechanism: 'plain', + username: 'username', + password: 'password', + }, + retry: { + maxRetryTime: { milliseconds: 20000 }, + initialRetryTime: { milliseconds: 200 }, + factor: '0.4', + multiplier: '4', + retries: '10', + }, + authenticationTimeout: { milliseconds: 20000 }, + connectionTimeout: { milliseconds: 1500 }, + requestTimeout: { milliseconds: 20000 }, + enforceRequestTimeout: false, + topics: [ + { + topic: 'fake1', + kafka: { + topic: 'topic-A', + allowAutoTopicCreation: true, + metadataMaxAge: { milliseconds: 400000 }, + transactionTimeout: { milliseconds: 30000 }, + idempotent: true, + maxInFlightRequests: 5, + retry: { + maxRetryTime: { milliseconds: 15000 }, + initialRetryTime: { milliseconds: 100 }, + factor: '0.2', + multiplier: '2', + retries: '5', + }, + }, + }, + { + topic: 'fake2', + kafka: { + topic: 'topic-B', + }, + }, + ], + }, + }, + }, + }, + }, + }); + + const publisherConfigs = readPublisherConfig(config); + + expect(publisherConfigs).toBeDefined(); + expect(Array.isArray(publisherConfigs)).toBe(true); + expect(publisherConfigs).toHaveLength(1); + + const devConfig = publisherConfigs[0]; + expect(devConfig.instance).toBe('dev'); + + // Client configuration + expect(devConfig.kafkaConfig.clientId).toEqual('backstage-events'); + expect(devConfig.kafkaConfig.brokers).toEqual([ + 'kafka1:9092', + 'kafka2:9092', + ]); + expect(devConfig.kafkaConfig.ssl).toBeTruthy(); + expect(devConfig.kafkaConfig.sasl).toStrictEqual({ + mechanism: 'plain', + username: 'username', + password: 'password', + }); + expect(devConfig.kafkaConfig.authenticationTimeout).toBe(20000); + expect(devConfig.kafkaConfig.connectionTimeout).toBe(1500); + expect(devConfig.kafkaConfig.requestTimeout).toBe(20000); + expect(devConfig.kafkaConfig.enforceRequestTimeout).toBeFalsy(); + expect(devConfig.kafkaConfig.retry).toStrictEqual({ + maxRetryTime: 20000, + initialRetryTime: 200, + factor: 0.4, + multiplier: 4, + retries: 10, + }); + + // Publisher configuration + expect(devConfig.kafkaPublisherConfigs.length).toBe(2); + + expect(devConfig.kafkaPublisherConfigs).toEqual([ + { + backstageTopic: 'fake1', + kafkaTopic: 'topic-A', + producerConfig: { + allowAutoTopicCreation: true, + metadataMaxAge: 400000, + transactionTimeout: 30000, + idempotent: true, + maxInFlightRequests: 5, + retry: { + maxRetryTime: 15000, + initialRetryTime: 100, + factor: 0.2, + multiplier: 2, + retries: 5, + }, + }, + }, + { + backstageTopic: 'fake2', + kafkaTopic: 'topic-B', + producerConfig: { + allowAutoTopicCreation: undefined, + metadataMaxAge: undefined, + transactionTimeout: undefined, + idempotent: undefined, + maxInFlightRequests: undefined, + retry: {}, + }, + }, + ]); + }); + + it('should handle HumanDuration and string values for durations and timeouts', () => { + const config = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaPublishingEventConsumer: { + dev: { + clientId: 'backstage-events', + brokers: ['kafka1:9092', 'kafka2:9092'], + retry: { + maxRetryTime: { seconds: 1 }, + initialRetryTime: { minutes: 1 }, + factor: 0.4, + multiplier: 4, + retries: 10, + }, + authenticationTimeout: { hours: 1 }, + connectionTimeout: { days: 1 }, + topics: [ + { + topic: 'fake1', + kafka: { + topic: 'topic-A', + metadataMaxAge: { seconds: 300 }, + transactionTimeout: '30s', + }, + }, + ], + requestTimeout: '1m', + }, + }, + }, + }, + }, + }); + + const publisherConfigs = readPublisherConfig(config); + + expect(publisherConfigs).toBeDefined(); + expect(Array.isArray(publisherConfigs)).toBe(true); + expect(publisherConfigs).toHaveLength(1); + + const devConfig = publisherConfigs[0]; + expect(devConfig.instance).toBe('dev'); + + // Client configuration + expect(devConfig.kafkaConfig.clientId).toEqual('backstage-events'); + expect(devConfig.kafkaConfig.brokers).toEqual([ + 'kafka1:9092', + 'kafka2:9092', + ]); + expect(devConfig.kafkaConfig.authenticationTimeout).toBe(3600000); + expect(devConfig.kafkaConfig.connectionTimeout).toBe(86400000); + expect(devConfig.kafkaConfig.requestTimeout).toBe(60000); + expect(devConfig.kafkaConfig.retry).toStrictEqual({ + maxRetryTime: 1000, + initialRetryTime: 60000, + factor: 0.4, + multiplier: 4, + retries: 10, + }); + + // Publisher configuration + expect(devConfig.kafkaPublisherConfigs.length).toBe(1); + expect( + devConfig.kafkaPublisherConfigs[0].producerConfig.metadataMaxAge, + ).toBe(300000); + expect( + devConfig.kafkaPublisherConfigs[0].producerConfig.transactionTimeout, + ).toBe(30000); + }); + + it('should handle multiple instances', () => { + const config = new ConfigReader({ + events: { + modules: { + kafka: { + kafkaPublishingEventConsumer: { + dev: { + clientId: 'backstage-dev', + brokers: ['kafka-dev:9092'], + topics: [ + { + topic: 'dev-topic', + kafka: { + topic: 'kafka-dev-topic', + }, + }, + ], + }, + prod: { + clientId: 'backstage-prod', + brokers: ['kafka-prod1:9092', 'kafka-prod2:9092'], + topics: [ + { + topic: 'prod-topic', + kafka: { + topic: 'kafka-prod-topic', + }, + }, + ], + }, + }, + }, + }, + }, + }); + + const publisherConfigs = readPublisherConfig(config); + + expect(publisherConfigs).toBeDefined(); + expect(Array.isArray(publisherConfigs)).toBe(true); + expect(publisherConfigs).toHaveLength(2); + + const devConfig = publisherConfigs.find(c => c.instance === 'dev')!; + expect(devConfig.kafkaConfig.clientId).toBe('backstage-dev'); + expect(devConfig.kafkaConfig.brokers).toEqual(['kafka-dev:9092']); + expect(devConfig.kafkaPublisherConfigs).toHaveLength(1); + expect(devConfig.kafkaPublisherConfigs[0].backstageTopic).toBe('dev-topic'); + + const prodConfig = publisherConfigs.find(c => c.instance === 'prod')!; + expect(prodConfig.kafkaConfig.clientId).toBe('backstage-prod'); + expect(prodConfig.kafkaConfig.brokers).toEqual([ + 'kafka-prod1:9092', + 'kafka-prod2:9092', + ]); + expect(prodConfig.kafkaPublisherConfigs).toHaveLength(1); + expect(prodConfig.kafkaPublisherConfigs[0].backstageTopic).toBe( + 'prod-topic', + ); + }); +}); diff --git a/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/config.ts b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/config.ts new file mode 100644 index 0000000000..8439e24405 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/config.ts @@ -0,0 +1,82 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Config } from '@backstage/config'; +import { + readKafkaConfig, + readOptionalHumanDurationInMs, + readRetryConfig, +} from '../utils/config'; +import { KafkaConfig, ProducerConfig } from 'kafkajs'; + +export interface KafkaPublisherConfig { + backstageTopic: string; + kafkaTopic: string; + producerConfig: ProducerConfig; +} + +export interface KafkaPublishingEventConsumerConfig { + instance: string; + kafkaConfig: KafkaConfig; + kafkaPublisherConfigs: KafkaPublisherConfig[]; +} + +const CONFIG_PREFIX_PUBLISHER = + 'events.modules.kafka.kafkaPublishingEventConsumer'; + +export const readPublisherConfig = ( + config: Config, +): KafkaPublishingEventConsumerConfig[] => { + const publishers = config.getOptionalConfig(CONFIG_PREFIX_PUBLISHER); + + return ( + publishers?.keys()?.map(publisherKey => { + const publisherConfig = publishers.getConfig(publisherKey); + + return { + instance: publisherKey, + kafkaConfig: readKafkaConfig(publisherConfig), + kafkaPublisherConfigs: publisherConfig + .getConfigArray('topics') + .map(topicConfig => { + return { + backstageTopic: topicConfig.getString('topic'), + kafkaTopic: topicConfig.getString('kafka.topic'), + producerConfig: { + allowAutoTopicCreation: topicConfig.getOptionalBoolean( + 'kafka.allowAutoTopicCreation', + ), + metadataMaxAge: readOptionalHumanDurationInMs( + topicConfig, + 'kafka.metadataMaxAge', + ), + transactionTimeout: readOptionalHumanDurationInMs( + topicConfig, + 'kafka.transactionTimeout', + ), + idempotent: topicConfig.getOptionalBoolean('kafka.idempotent'), + maxInFlightRequests: topicConfig.getOptionalNumber( + 'kafka.maxInFlightRequests', + ), + retry: readRetryConfig( + topicConfig.getOptionalConfig('kafka.retry'), + ), + }, + }; + }), + }; + }) ?? [] + ); +}; diff --git a/packages/frontend-plugin-api/src/apis/definitions/alpha.ts b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/index.ts similarity index 82% rename from packages/frontend-plugin-api/src/apis/definitions/alpha.ts rename to plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/index.ts index a4ccde1170..42fadd2a5a 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/alpha.ts +++ b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * 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. @@ -13,5 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -export { appLanguageApiRef, type AppLanguageApi } from './AppLanguageApi'; +export { eventsModuleKafkaPublishingEventConsumer } from './module'; diff --git a/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/module.test.ts b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/module.test.ts new file mode 100644 index 0000000000..39c8268300 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/module.test.ts @@ -0,0 +1,122 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createServiceFactory } from '@backstage/backend-plugin-api'; +import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; +import { eventsServiceRef } from '@backstage/plugin-events-node'; +import { TestEventsService } from '@backstage/plugin-events-backend-test-utils'; +import { eventsModuleKafkaPublishingEventConsumer } from './module'; +import { KafkaPublishingEventConsumer } from './KafkaPublishingEventConsumer'; + +jest.mock('./KafkaPublishingEventConsumer'); + +describe('eventsModuleKafkaPublishingEventConsumer', () => { + it('should be correctly wired and set up', async () => { + const events = new TestEventsService(); + const eventsServiceFactory = createServiceFactory({ + service: eventsServiceRef, + deps: {}, + async factory({}) { + return events; + }, + }); + + const mockKafkaPublishingEventConsumer = { + start: jest.fn(), + shutdown: jest.fn(), + } as unknown as KafkaPublishingEventConsumer; + + jest + .mocked(KafkaPublishingEventConsumer.fromConfig) + .mockReturnValue([mockKafkaPublishingEventConsumer]); + + await startTestBackend({ + features: [ + eventsServiceFactory, + eventsModuleKafkaPublishingEventConsumer, + mockServices.rootConfig.factory({ + data: { + events: { + modules: { + kafka: { + kafkaPublishingEventConsumer: { + dev: { + clientId: 'backstage-events', + brokers: ['kafka1:9092', 'kafka2:9092'], + topics: [ + { + topic: 'fake1', + kafka: { + topic: 'topic-A', + }, + }, + { + topic: 'fake2', + kafka: { + topic: 'topic-B', + }, + }, + ], + }, + }, + }, + }, + }, + }, + }), + ], + }); + + // Verify that the Kafka publishing event consumer was started + expect(mockKafkaPublishingEventConsumer.start).toHaveBeenCalled(); + + // Verify that the shutdown hook was registered (but not called yet) + expect(mockKafkaPublishingEventConsumer.shutdown).not.toHaveBeenCalled(); + }); + + it('should handle empty configuration gracefully', async () => { + const events = new TestEventsService(); + const eventsServiceFactory = createServiceFactory({ + service: eventsServiceRef, + deps: {}, + async factory({}) { + return events; + }, + }); + + jest.mocked(KafkaPublishingEventConsumer.fromConfig).mockReturnValue([]); + + await startTestBackend({ + features: [ + eventsServiceFactory, + eventsModuleKafkaPublishingEventConsumer, + mockServices.rootConfig.factory({ + data: { + events: { + modules: { + kafka: { + // No kafkaPublishingEventConsumer config + }, + }, + }, + }, + }), + ], + }); + + // Verify that fromConfig was called but returned empty array + expect(KafkaPublishingEventConsumer.fromConfig).toHaveBeenCalled(); + }); +}); diff --git a/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/module.ts b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/module.ts new file mode 100644 index 0000000000..dd5f0c50ab --- /dev/null +++ b/plugins/events-backend-module-kafka/src/KafkaPublishingEventConsumer/module.ts @@ -0,0 +1,56 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + coreServices, + createBackendModule, +} from '@backstage/backend-plugin-api'; +import { eventsServiceRef } from '@backstage/plugin-events-node'; +import { KafkaPublishingEventConsumer } from './KafkaPublishingEventConsumer'; + +/** + * Reads internal Backstage events and forwards them to Kafka topics. + * + * @public + */ +export const eventsModuleKafkaPublishingEventConsumer = createBackendModule({ + pluginId: 'events', + moduleId: 'kafka-publishing-event-consumer', + register(env) { + env.registerInit({ + deps: { + config: coreServices.rootConfig, + events: eventsServiceRef, + logger: coreServices.logger, + lifecycle: coreServices.rootLifecycle, + }, + async init({ config, logger, events, lifecycle }) { + const consumers = KafkaPublishingEventConsumer.fromConfig({ + config, + events, + logger, + }); + + lifecycle.addStartupHook(async () => { + await Promise.all(consumers.map(consumer => consumer.start())); + }); + + lifecycle.addShutdownHook(async () => { + await Promise.all(consumers.map(consumer => consumer.shutdown())); + }); + }, + }); + }, +}); diff --git a/plugins/events-backend-module-kafka/src/index.ts b/plugins/events-backend-module-kafka/src/index.ts index 3ba6207d7d..af4fb02bf1 100644 --- a/plugins/events-backend-module-kafka/src/index.ts +++ b/plugins/events-backend-module-kafka/src/index.ts @@ -13,14 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { createBackendFeatureLoader } from '@backstage/backend-plugin-api'; +import { eventsModuleKafkaConsumingEventPublisher } from './KafkaConsumingEventPublisher'; +import { eventsModuleKafkaPublishingEventConsumer } from './KafkaPublishingEventConsumer'; /** * The module "kafka" for the Backstage backend plugin "events" - * adding an Kafka-based publisher, - * receiving events from an Kafka topic and passing it to the - * internal event broker. + * adding Kafka-based event handling: + * - Consumer: receives events from Kafka topics and passes them to the internal event broker + * - Publisher: receives internal events and publishes them to Kafka topics * * @packageDocumentation */ -export { eventsModuleKafkaConsumingEventPublisher as default } from './service/eventsModuleKafkaConsumingEventPublisher'; +export default createBackendFeatureLoader({ + *loader() { + yield eventsModuleKafkaConsumingEventPublisher; + yield eventsModuleKafkaPublishingEventConsumer; + }, +}); diff --git a/plugins/events-backend-module-kafka/src/publisher/KafkaConsumerClient.test.ts b/plugins/events-backend-module-kafka/src/publisher/KafkaConsumerClient.test.ts deleted file mode 100644 index 5824bf4d41..0000000000 --- a/plugins/events-backend-module-kafka/src/publisher/KafkaConsumerClient.test.ts +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2025 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { KafkaConsumerClient } from './KafkaConsumerClient'; -import { ConfigReader } from '@backstage/config'; -import { KafkaConsumingEventPublisher } from './KafkaConsumingEventPublisher'; -import { mockServices } from '@backstage/backend-test-utils'; - -jest.mock('kafkajs'); -jest.mock('./KafkaConsumingEventPublisher'); - -describe('KafkaConsumerClient', () => { - const mockLogger = mockServices.logger.mock(); - const mockEvents = mockServices.events.mock(); - - const mockConfig = new ConfigReader({ - events: { - modules: { - kafka: { - kafkaConsumingEventPublisher: { - clientId: 'backstage-events', - brokers: ['kafka1:9092', 'kafka2:9092'], - topics: [ - { - topic: 'fake1', - kafka: { - topics: ['topic-A'], - groupId: 'my-group', - }, - }, - { - topic: 'fake2', - kafka: { - topics: ['topic-B'], - groupId: 'my-group', - }, - }, - ], - }, - }, - }, - }, - }); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should create an instance from config', () => { - const client = KafkaConsumerClient.fromConfig({ - config: mockConfig, - events: mockEvents, - logger: mockLogger, - }); - - expect(client).toBeInstanceOf(KafkaConsumerClient); - }); - - it('should not create an instance from config', () => { - const client = KafkaConsumerClient.fromConfig({ - config: new ConfigReader({}), - events: mockEvents, - logger: mockLogger, - }); - - expect(client).toBeUndefined(); - }); - - it('should create a consumer for each topic from config', () => { - KafkaConsumerClient.fromConfig({ - config: mockConfig, - events: mockEvents, - logger: mockLogger, - }); - - expect(KafkaConsumingEventPublisher.fromConfig).toHaveBeenCalledTimes(2); - }); - - it('should start all consumers', async () => { - const mockConsumer = { - start: jest.fn().mockResolvedValue(undefined), - }; - (KafkaConsumingEventPublisher.fromConfig as jest.Mock).mockReturnValue( - mockConsumer, - ); - - const client = KafkaConsumerClient.fromConfig({ - config: mockConfig, - events: mockEvents, - logger: mockLogger, - }); - - expect(client).toBeDefined(); - - await client?.start(); - - expect(mockConsumer.start).toHaveBeenCalled(); - }); - - it('should shutdown all consumers', async () => { - const mockConsumer = { - shutdown: jest.fn().mockResolvedValue(undefined), - }; - (KafkaConsumingEventPublisher.fromConfig as jest.Mock).mockReturnValue( - mockConsumer, - ); - - const client = KafkaConsumerClient.fromConfig({ - config: mockConfig, - events: mockEvents, - logger: mockLogger, - }); - - expect(client).toBeDefined(); - - await client?.shutdown(); - - expect(mockConsumer.shutdown).toHaveBeenCalled(); - }); -}); diff --git a/plugins/events-backend-module-kafka/src/publisher/KafkaConsumerClient.ts b/plugins/events-backend-module-kafka/src/publisher/KafkaConsumerClient.ts deleted file mode 100644 index d506e4a6af..0000000000 --- a/plugins/events-backend-module-kafka/src/publisher/KafkaConsumerClient.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2025 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { LoggerService } from '@backstage/backend-plugin-api'; -import { Config } from '@backstage/config'; -import { EventsService } from '@backstage/plugin-events-node'; -import { Kafka } from 'kafkajs'; -import { KafkaEventSourceConfig, readConfig } from './config'; -import { KafkaConsumingEventPublisher } from './KafkaConsumingEventPublisher'; -import { loggerServiceAdapter } from './LoggerServiceAdapter'; - -/** - * KafkaConsumerClient - * - * This class creates the Kafka client that will be used to create the KafkaConsumingEventPublisher - */ -export class KafkaConsumerClient { - private readonly kafka: Kafka; - private readonly consumers: KafkaConsumingEventPublisher[]; - - static fromConfig(options: { - config: Config; - events: EventsService; - logger: LoggerService; - }): KafkaConsumerClient | undefined { - const kafkaConfig = readConfig(options.config); - - if (!kafkaConfig) { - options.logger.info( - 'Kafka consumer not configured, skipping initialization', - ); - return undefined; - } - - return new KafkaConsumerClient(options.logger, options.events, kafkaConfig); - } - - private constructor( - logger: LoggerService, - events: EventsService, - config: KafkaEventSourceConfig, - ) { - this.kafka = new Kafka({ - ...config.kafkaConfig, - logCreator: loggerServiceAdapter(logger), - }); - - this.consumers = config.kafkaConsumerConfigs.map(consumerConfig => - KafkaConsumingEventPublisher.fromConfig({ - kafkaClient: this.kafka, - config: consumerConfig, - logger, - events, - }), - ); - } - - async start(): Promise { - this.consumers.map(async consumer => await consumer.start()); - } - - async shutdown(): Promise { - this.consumers.map(async consumer => await consumer.shutdown()); - } -} diff --git a/plugins/events-backend-module-kafka/src/publisher/KafkaConsumingEventPublisher.test.ts b/plugins/events-backend-module-kafka/src/publisher/KafkaConsumingEventPublisher.test.ts deleted file mode 100644 index 64c53595ad..0000000000 --- a/plugins/events-backend-module-kafka/src/publisher/KafkaConsumingEventPublisher.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2025 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { KafkaConsumingEventPublisher } from './KafkaConsumingEventPublisher'; -import { Kafka } from 'kafkajs'; -import { KafkaConsumerConfig } from './config'; -import { mockServices } from '@backstage/backend-test-utils'; - -jest.mock('kafkajs'); - -describe('KafkaConsumingEventPublisher', () => { - const mockLogger = mockServices.logger.mock(); - const mockEvents = mockServices.events.mock(); - - const mockConsumer = { - connect: jest.fn(), - disconnect: jest.fn(), - subscribe: jest.fn(), - run: jest.fn(), - }; - - const mockKafkaClient = { - consumer: jest.fn().mockReturnValue(mockConsumer), - } as unknown as Kafka; - - const kafkaConsumerConfig: KafkaConsumerConfig = { - consumerConfig: { - groupId: 'test-group', - }, - consumerSubscribeTopics: { - topics: ['test-topic'], - }, - backstageTopic: 'backstage-topic', - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should create an instance from config', () => { - const consumer = KafkaConsumingEventPublisher.fromConfig({ - kafkaClient: mockKafkaClient, - config: kafkaConsumerConfig, - events: mockEvents, - logger: mockLogger, - }); - - expect(consumer).toBeInstanceOf(KafkaConsumingEventPublisher); - }); - - it('should start the consumer', async () => { - const consumer = KafkaConsumingEventPublisher.fromConfig({ - kafkaClient: mockKafkaClient, - config: kafkaConsumerConfig, - events: mockEvents, - logger: mockLogger, - }); - - await consumer.start(); - - expect(mockConsumer.connect).toHaveBeenCalled(); - expect(mockConsumer.subscribe).toHaveBeenCalledWith( - kafkaConsumerConfig.consumerSubscribeTopics, - ); - expect(mockConsumer.run).toHaveBeenCalled(); - }); - - it('should shutdown the consumer', async () => { - const consumer = KafkaConsumingEventPublisher.fromConfig({ - kafkaClient: mockKafkaClient, - config: kafkaConsumerConfig, - events: mockEvents, - logger: mockLogger, - }); - - await consumer.shutdown(); - - expect(mockConsumer.disconnect).toHaveBeenCalled(); - }); -}); diff --git a/plugins/events-backend-module-kafka/src/publisher/KafkaConsumingEventPublisher.ts b/plugins/events-backend-module-kafka/src/publisher/KafkaConsumingEventPublisher.ts deleted file mode 100644 index dfc6186fb5..0000000000 --- a/plugins/events-backend-module-kafka/src/publisher/KafkaConsumingEventPublisher.ts +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2025 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { LoggerService } from '@backstage/backend-plugin-api'; -import { EventParams, EventsService } from '@backstage/plugin-events-node'; -import { Consumer, ConsumerSubscribeTopics, IHeaders, Kafka } from 'kafkajs'; -import { KafkaConsumerConfig } from './config'; - -type EventMetadata = EventParams['metadata']; - -/** - * This class subscribes to Kafka topics and publishes events received to the registered subscriber. - * The message payload will be used as the event payload and passed to the subscribers. - */ -export class KafkaConsumingEventPublisher { - private readonly kafkaConsumer: Consumer; - private readonly consumerSubscribeTopics: ConsumerSubscribeTopics; - private readonly backstageTopic: string; - private readonly logger: LoggerService; - - static fromConfig(env: { - kafkaClient: Kafka; - config: KafkaConsumerConfig; - events: EventsService; - logger: LoggerService; - }): KafkaConsumingEventPublisher { - return new KafkaConsumingEventPublisher( - env.kafkaClient, - env.logger, - env.events, - env.config, - ); - } - - private readonly events: EventsService; - - private constructor( - kafkaClient: Kafka, - logger: LoggerService, - events: EventsService, - config: KafkaConsumerConfig, - ) { - this.events = events; - this.kafkaConsumer = kafkaClient.consumer(config.consumerConfig); - this.consumerSubscribeTopics = config.consumerSubscribeTopics; - this.backstageTopic = config.backstageTopic; - const id = `events.kafka.publisher:${this.backstageTopic}`; - this.logger = logger.child({ - class: KafkaConsumingEventPublisher.prototype.constructor.name, - groupId: config.consumerConfig.groupId, - kafkaTopics: config.consumerSubscribeTopics.topics.toString(), - backstageTopic: config.backstageTopic, - taskId: id, - }); - } - - async start(): Promise { - try { - await this.kafkaConsumer.connect(); - - await this.kafkaConsumer.subscribe(this.consumerSubscribeTopics); - - await this.kafkaConsumer.run({ - eachMessage: async ({ message }) => { - this.events.publish({ - topic: this.backstageTopic, - eventPayload: JSON.parse(message.value?.toString()!), - metadata: this.convertHeadersToMetadata(message.headers), - }); - }, - }); - } catch (error: any) { - this.logger.error('Kafka consumer connection failed ', error); - } - } - - async shutdown(): Promise { - await this.kafkaConsumer.disconnect(); - } - - private convertHeadersToMetadata = ( - headers: IHeaders | undefined, - ): EventParams['metadata'] => { - if (!headers) return undefined; - - const metadata: EventMetadata = {}; - - Object.entries(headers).forEach(([key, value]) => { - // If value is an array use toString() on all values converting any Buffer types to valid strings - if (Array.isArray(value)) metadata[key] = value.map(v => v.toString()); - // Always return the values using toString() to catch all Buffer types that should be converted to strings - else metadata[key] = value?.toString(); - }); - - return metadata; - }; -} diff --git a/plugins/events-backend-module-kafka/src/publisher/config.test.ts b/plugins/events-backend-module-kafka/src/publisher/config.test.ts deleted file mode 100644 index fc7e3049b0..0000000000 --- a/plugins/events-backend-module-kafka/src/publisher/config.test.ts +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright 2025 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { ConfigReader } from '@backstage/config'; -import { readConfig } from './config'; - -describe('readConfig', () => { - it('not configured', () => { - const publisherConfigs = readConfig(new ConfigReader({})); - - expect(publisherConfigs).toBeUndefined(); - }); - - it('only required fields configured', () => { - const config = new ConfigReader({ - events: { - modules: { - kafka: { - kafkaConsumingEventPublisher: { - clientId: 'backstage-events', - brokers: ['kafka1:9092', 'kafka2:9092'], - topics: [ - { - topic: 'fake1', - kafka: { - topics: ['topic-A'], - groupId: 'my-group', - }, - }, - { - topic: 'fake2', - kafka: { - topics: ['topic-B'], - groupId: 'my-group', - }, - }, - ], - }, - }, - }, - }, - }); - - const publisherConfigs = readConfig(config); - - expect(publisherConfigs).toBeDefined(); - - expect(publisherConfigs?.kafkaConsumerConfigs.length).toBe(2); - - expect(publisherConfigs?.kafkaConfig.clientId).toEqual('backstage-events'); - expect(publisherConfigs?.kafkaConfig.brokers).toEqual([ - 'kafka1:9092', - 'kafka2:9092', - ]); - expect(publisherConfigs?.kafkaConsumerConfigs[0].backstageTopic).toEqual( - 'fake1', - ); - expect( - publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.groupId, - ).toEqual('my-group'); - expect( - publisherConfigs?.kafkaConsumerConfigs[0].consumerSubscribeTopics.topics, - ).toEqual(['topic-A']); - }); - - it('all fields configured', () => { - const config = new ConfigReader({ - events: { - modules: { - kafka: { - kafkaConsumingEventPublisher: { - clientId: 'backstage-events', - brokers: ['kafka1:9092', 'kafka2:9092'], - ssl: true, - sasl: { - mechanism: 'plain', - username: 'username', - password: 'password', - }, - retry: { - maxRetryTime: { milliseconds: 20000 }, - initialRetryTime: { milliseconds: 200 }, - factor: '0.4', - multiplier: '4', - retries: '10', - }, - authenticationTimeout: { milliseconds: 20000 }, - connectionTimeout: { milliseconds: 1500 }, - requestTimeout: { milliseconds: 20000 }, - enforceRequestTimeout: false, - topics: [ - { - topic: 'fake1', - kafka: { - topics: ['topic-A'], - groupId: 'my-group', - sessionTimeout: { milliseconds: 20000 }, - rebalanceTimeout: { milliseconds: 50000 }, - heartbeatInterval: { milliseconds: 2000 }, - metadataMaxAge: { milliseconds: 400000 }, - maxBytesPerPartition: 50000, - minBytes: 2, - maxBytes: 500000, - maxWaitTime: { milliseconds: 4000 }, - }, - }, - { - topic: 'fake2', - kafka: { - topics: ['topic-B'], - groupId: 'my-group', - }, - }, - ], - }, - }, - }, - }, - }); - - const publisherConfigs = readConfig(config); - - expect(publisherConfigs).toBeDefined(); - - // Client configuration - expect(publisherConfigs?.kafkaConfig.clientId).toEqual('backstage-events'); - expect(publisherConfigs?.kafkaConfig.brokers).toEqual([ - 'kafka1:9092', - 'kafka2:9092', - ]); - expect(publisherConfigs?.kafkaConfig.ssl).toBeTruthy(); - expect(publisherConfigs?.kafkaConfig.sasl).toStrictEqual({ - mechanism: 'plain', - username: 'username', - password: 'password', - }); - expect(publisherConfigs?.kafkaConfig.authenticationTimeout).toBe(20000); - expect(publisherConfigs?.kafkaConfig.connectionTimeout).toBe(1500); - expect(publisherConfigs?.kafkaConfig.requestTimeout).toBe(20000); - expect(publisherConfigs?.kafkaConfig.enforceRequestTimeout).toBeFalsy(); - expect(publisherConfigs?.kafkaConfig.retry).toStrictEqual({ - maxRetryTime: 20000, - initialRetryTime: 200, - factor: 0.4, - multiplier: 4, - retries: 10, - }); - - // Consumer configuration - expect(publisherConfigs?.kafkaConsumerConfigs.length).toBe(2); - expect(publisherConfigs?.kafkaConsumerConfigs[0].backstageTopic).toEqual( - 'fake1', - ); - expect( - publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.groupId, - ).toEqual('my-group'); - expect( - publisherConfigs?.kafkaConsumerConfigs[0].consumerSubscribeTopics.topics, - ).toEqual(['topic-A']); - - expect( - publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.sessionTimeout, - ).toBe(20000); - expect( - publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.rebalanceTimeout, - ).toBe(50000); - expect( - publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig - .heartbeatInterval, - ).toBe(2000); - expect( - publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.metadataMaxAge, - ).toBe(400000); - expect( - publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig - .maxBytesPerPartition, - ).toBe(50000); - expect( - publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.minBytes, - ).toBe(2); - expect( - publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.maxBytes, - ).toBe(500000); - expect( - publisherConfigs?.kafkaConsumerConfigs[0].consumerConfig.maxWaitTimeInMs, - ).toBe(4000); - }); - - it('should handle HumanDuration and string values for durations and timeouts', () => { - const config = new ConfigReader({ - events: { - modules: { - kafka: { - kafkaConsumingEventPublisher: { - clientId: 'backstage-events', - brokers: ['kafka1:9092', 'kafka2:9092'], - retry: { - maxRetryTime: { seconds: 1 }, - initialRetryTime: { minutes: 1 }, - factor: 0.4, - multiplier: 4, - retries: 10, - }, - authenticationTimeout: { hours: 1 }, - connectionTimeout: { days: 1 }, - topics: [], - requestTimeout: '1m', - }, - }, - }, - }, - }); - - const publisherConfigs = readConfig(config); - - expect(publisherConfigs).toBeDefined(); - - // Client configuration - expect(publisherConfigs?.kafkaConfig.clientId).toEqual('backstage-events'); - expect(publisherConfigs?.kafkaConfig.brokers).toEqual([ - 'kafka1:9092', - 'kafka2:9092', - ]); - expect(publisherConfigs?.kafkaConfig.authenticationTimeout).toBe(3600000); - expect(publisherConfigs?.kafkaConfig.connectionTimeout).toBe(86400000); - expect(publisherConfigs?.kafkaConfig.requestTimeout).toBe(60000); - expect(publisherConfigs?.kafkaConfig.retry).toStrictEqual({ - maxRetryTime: 1000, - initialRetryTime: 60000, - factor: 0.4, - multiplier: 4, - retries: 10, - }); - - // Consumer configuration - expect(publisherConfigs?.kafkaConsumerConfigs.length).toBe(0); - }); -}); diff --git a/plugins/events-backend-module-kafka/src/publisher/config.ts b/plugins/events-backend-module-kafka/src/publisher/config.ts deleted file mode 100644 index 7534ce4d38..0000000000 --- a/plugins/events-backend-module-kafka/src/publisher/config.ts +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2025 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { Config, readDurationFromConfig } from '@backstage/config'; -import { durationToMilliseconds } from '@backstage/types'; -import { ConsumerConfig, ConsumerSubscribeTopics, KafkaConfig } from 'kafkajs'; - -export interface KafkaConsumerConfig { - backstageTopic: string; - consumerConfig: ConsumerConfig; - consumerSubscribeTopics: ConsumerSubscribeTopics; -} - -export interface KafkaEventSourceConfig { - kafkaConfig: KafkaConfig; - kafkaConsumerConfigs: KafkaConsumerConfig[]; -} - -const CONFIG_PREFIX_PUBLISHER = - 'events.modules.kafka.kafkaConsumingEventPublisher'; - -/** - * Reads an optional HumanDuration from the config and returns the value in milliseconds if the key is defined. - * - * @param config - The configuration object to read from. - * @param key - The key to look up in the configuration. - * @returns The duration in milliseconds, or undefined if the key is not defined. - */ -const readOptionalHumanDurationInMs = ( - config: Config, - key: string, -): number | undefined => { - const humanDuration = config.has(key) - ? readDurationFromConfig(config, { key }) - : undefined; - - if (!humanDuration) return undefined; - - return durationToMilliseconds(humanDuration); -}; - -export const readConfig = ( - config: Config, -): KafkaEventSourceConfig | undefined => { - const kafkaConfig = config.getOptionalConfig(CONFIG_PREFIX_PUBLISHER); - - if (!kafkaConfig) { - return undefined; - } - - const clientId = kafkaConfig.getString('clientId'); - const brokers = kafkaConfig.getStringArray('brokers'); - - const authenticationTimeout = readOptionalHumanDurationInMs( - kafkaConfig, - 'authenticationTimeout', - ); - - const connectionTimeout = readOptionalHumanDurationInMs( - kafkaConfig, - 'connectionTimeout', - ); - const requestTimeout = readOptionalHumanDurationInMs( - kafkaConfig, - 'requestTimeout', - ); - const enforceRequestTimeout = kafkaConfig.getOptionalBoolean( - 'enforceRequestTimeout', - ); - - const ssl = kafkaConfig.getOptional('ssl') as KafkaConfig['ssl']; - const sasl = kafkaConfig.getOptional('sasl') as KafkaConfig['sasl']; - - const retry: KafkaConfig['retry'] = { - maxRetryTime: readOptionalHumanDurationInMs( - kafkaConfig, - 'retry.maxRetryTime', - ), - initialRetryTime: readOptionalHumanDurationInMs( - kafkaConfig, - 'retry.initialRetryTime', - ), - factor: kafkaConfig.getOptionalNumber('retry.factor'), - multiplier: kafkaConfig.getOptionalNumber('retry.multiplier'), - retries: kafkaConfig.getOptionalNumber('retry.retries'), - }; - - const kafkaConsumerConfigs: KafkaConsumerConfig[] = kafkaConfig - .getConfigArray('topics') - .map(topic => { - return { - backstageTopic: topic.getString('topic'), - consumerConfig: { - groupId: topic.getString('kafka.groupId'), - sessionTimeout: readOptionalHumanDurationInMs( - topic, - 'kafka.sessionTimeout', - ), - rebalanceTimeout: readOptionalHumanDurationInMs( - topic, - 'kafka.rebalanceTimeout', - ), - heartbeatInterval: readOptionalHumanDurationInMs( - topic, - 'kafka.heartbeatInterval', - ), - metadataMaxAge: readOptionalHumanDurationInMs( - topic, - 'kafka.metadataMaxAge', - ), - maxBytesPerPartition: topic.getOptionalNumber( - 'kafka.maxBytesPerPartition', - ), - minBytes: topic.getOptionalNumber('kafka.minBytes'), - maxBytes: topic.getOptionalNumber('kafka.maxBytes'), - maxWaitTimeInMs: readOptionalHumanDurationInMs( - topic, - 'kafka.maxWaitTime', - ), - }, - consumerSubscribeTopics: { - topics: topic.getStringArray('kafka.topics'), - }, - }; - }); - - return { - kafkaConfig: { - clientId, - brokers, - ssl, - sasl, - authenticationTimeout, - connectionTimeout, - requestTimeout, - enforceRequestTimeout, - retry, - }, - kafkaConsumerConfigs, - }; -}; diff --git a/plugins/events-backend-module-kafka/src/service/eventsModuleKafkaConsumingEventPublisher.test.ts b/plugins/events-backend-module-kafka/src/service/eventsModuleKafkaConsumingEventPublisher.test.ts deleted file mode 100644 index 816793f635..0000000000 --- a/plugins/events-backend-module-kafka/src/service/eventsModuleKafkaConsumingEventPublisher.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2025 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { createServiceFactory } from '@backstage/backend-plugin-api'; -import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; -import { eventsServiceRef } from '@backstage/plugin-events-node'; -import { TestEventsService } from '@backstage/plugin-events-backend-test-utils'; -import { eventsModuleKafkaConsumingEventPublisher } from './eventsModuleKafkaConsumingEventPublisher'; -import { KafkaConsumerClient } from '../publisher/KafkaConsumerClient'; - -jest.mock('../publisher/KafkaConsumerClient'); - -describe('eventsModuleKafkaConsumingEventPublisher', () => { - it('should be correctly wired and set up', async () => { - const events = new TestEventsService(); - const eventsServiceFactory = createServiceFactory({ - service: eventsServiceRef, - deps: {}, - async factory({}) { - return events; - }, - }); - - const mockKafkaConsumerClient = { - start: jest.fn(), - shutdown: jest.fn(), - }; - (KafkaConsumerClient.fromConfig as jest.Mock).mockReturnValue( - mockKafkaConsumerClient, - ); - - await startTestBackend({ - features: [ - eventsServiceFactory, - eventsModuleKafkaConsumingEventPublisher, - mockServices.rootConfig.factory({ - data: { - events: { - modules: { - kafka: { - kafkaConsumingEventPublisher: { - clientId: 'backstage-events', - brokers: ['kafka1:9092', 'kafka2:9092'], - topics: { - fake1: { - kafka: { - topics: ['topic-A'], - groupId: 'my-group', - }, - }, - fake2: { - kafka: { - topics: ['topic-B'], - groupId: 'my-group', - }, - }, - }, - }, - }, - }, - }, - }, - }), - ], - }); - - // Verify that the Kafka consumer client was started - expect(mockKafkaConsumerClient.start).toHaveBeenCalled(); - - // Verify that the shutdown hook was registered - expect(mockKafkaConsumerClient.shutdown).not.toHaveBeenCalled(); - }); -}); diff --git a/plugins/events-backend-module-kafka/src/publisher/LoggerServiceAdapter.ts b/plugins/events-backend-module-kafka/src/utils/LoggerServiceAdapter.ts similarity index 100% rename from plugins/events-backend-module-kafka/src/publisher/LoggerServiceAdapter.ts rename to plugins/events-backend-module-kafka/src/utils/LoggerServiceAdapter.ts diff --git a/plugins/events-backend-module-kafka/src/utils/config.test.ts b/plugins/events-backend-module-kafka/src/utils/config.test.ts new file mode 100644 index 0000000000..2bd6121ba2 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/utils/config.test.ts @@ -0,0 +1,235 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ConfigReader } from '@backstage/config'; +import { readRetryConfig, readKafkaConfig } from './config'; + +describe('readRetryConfig', () => { + it('should return empty object when config is undefined', () => { + const result = readRetryConfig(undefined); + expect(result).toEqual({}); + }); + + it('should return empty object when config is empty', () => { + const config = new ConfigReader({}); + const result = readRetryConfig(config); + expect(result).toEqual({}); + }); + + it('should read retry configuration with milliseconds', () => { + const config = new ConfigReader({ + maxRetryTime: { milliseconds: 20000 }, + initialRetryTime: { milliseconds: 200 }, + factor: 0.4, + multiplier: 4, + retries: 10, + }); + + const result = readRetryConfig(config); + + expect(result).toEqual({ + maxRetryTime: 20000, + initialRetryTime: 200, + factor: 0.4, + multiplier: 4, + retries: 10, + }); + }); + + it('should read retry configuration with string values', () => { + const config = new ConfigReader({ + maxRetryTime: { seconds: 20 }, + initialRetryTime: { minutes: 1 }, + factor: '0.4', + multiplier: '4', + retries: '10', + }); + + const result = readRetryConfig(config); + + expect(result).toEqual({ + maxRetryTime: 20000, + initialRetryTime: 60000, + factor: 0.4, + multiplier: 4, + retries: 10, + }); + }); + + it('should handle HumanDuration values', () => { + const config = new ConfigReader({ + maxRetryTime: { hours: 1 }, + initialRetryTime: { days: 1 }, + }); + + const result = readRetryConfig(config); + + expect(result).toEqual({ + maxRetryTime: 3600000, + initialRetryTime: 86400000, + factor: undefined, + multiplier: undefined, + retries: undefined, + }); + }); + + it('should handle partial configuration', () => { + const config = new ConfigReader({ + maxRetryTime: { milliseconds: 15000 }, + retries: 5, + }); + + const result = readRetryConfig(config); + + expect(result).toEqual({ + maxRetryTime: 15000, + initialRetryTime: undefined, + factor: undefined, + multiplier: undefined, + retries: 5, + }); + }); +}); + +describe('readKafkaConfig', () => { + it('should read minimal kafka configuration', () => { + const config = new ConfigReader({ + clientId: 'test-client', + brokers: ['kafka1:9092', 'kafka2:9092'], + }); + + const result = readKafkaConfig(config); + + expect(result).toEqual({ + clientId: 'test-client', + brokers: ['kafka1:9092', 'kafka2:9092'], + authenticationTimeout: undefined, + connectionTimeout: undefined, + requestTimeout: undefined, + enforceRequestTimeout: undefined, + ssl: undefined, + sasl: undefined, + retry: {}, + }); + }); + + it('should read full kafka configuration with all optional fields', () => { + const config = new ConfigReader({ + clientId: 'backstage-events', + brokers: ['kafka1:9092', 'kafka2:9092'], + authenticationTimeout: { milliseconds: 20000 }, + connectionTimeout: { milliseconds: 1500 }, + requestTimeout: { milliseconds: 20000 }, + enforceRequestTimeout: false, + ssl: true, + sasl: { + mechanism: 'plain', + username: 'username', + password: 'password', + }, + retry: { + maxRetryTime: { milliseconds: 20000 }, + initialRetryTime: { milliseconds: 200 }, + factor: 0.4, + multiplier: 4, + retries: 10, + }, + }); + + const result = readKafkaConfig(config); + + expect(result).toEqual({ + clientId: 'backstage-events', + brokers: ['kafka1:9092', 'kafka2:9092'], + authenticationTimeout: 20000, + connectionTimeout: 1500, + requestTimeout: 20000, + enforceRequestTimeout: false, + ssl: true, + sasl: { + mechanism: 'plain', + username: 'username', + password: 'password', + }, + retry: { + maxRetryTime: 20000, + initialRetryTime: 200, + factor: 0.4, + multiplier: 4, + retries: 10, + }, + }); + }); + + it('should handle HumanDuration values for timeouts', () => { + const config = new ConfigReader({ + clientId: 'test-client', + brokers: ['kafka:9092'], + authenticationTimeout: { hours: 1 }, + connectionTimeout: { days: 1 }, + requestTimeout: { minutes: 5 }, + }); + + const result = readKafkaConfig(config); + + expect(result.authenticationTimeout).toBe(3600000); + expect(result.connectionTimeout).toBe(86400000); + expect(result.requestTimeout).toBe(300000); + }); + + it('should handle complex SSL configuration', () => { + const config = new ConfigReader({ + clientId: 'secure-client', + brokers: ['secure-kafka:9093'], + ssl: { + rejectUnauthorized: false, + ca: 'ca-certificate', + key: 'client-key', + cert: 'client-cert', + }, + }); + + const result = readKafkaConfig(config); + + expect(result.ssl).toEqual({ + rejectUnauthorized: false, + ca: 'ca-certificate', + key: 'client-key', + cert: 'client-cert', + }); + }); + + it('should handle complex SASL configuration', () => { + const config = new ConfigReader({ + clientId: 'sasl-client', + brokers: ['sasl-kafka:9094'], + sasl: { + mechanism: 'scram-sha-256', + username: 'kafka-user', + password: 'kafka-password', + authorizationIdentity: 'authz-user', + }, + }); + + const result = readKafkaConfig(config); + + expect(result.sasl).toEqual({ + mechanism: 'scram-sha-256', + username: 'kafka-user', + password: 'kafka-password', + authorizationIdentity: 'authz-user', + }); + }); +}); diff --git a/plugins/events-backend-module-kafka/src/utils/config.ts b/plugins/events-backend-module-kafka/src/utils/config.ts new file mode 100644 index 0000000000..4e140d26a3 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/utils/config.ts @@ -0,0 +1,84 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Config, readDurationFromConfig } from '@backstage/config'; +import { durationToMilliseconds } from '@backstage/types'; +import { KafkaConfig, RetryOptions } from 'kafkajs'; + +/** + * Reads an optional HumanDuration from the config and returns the value in milliseconds if the key is defined. + * + * @param config - The configuration object to read from. + * @param key - The key to look up in the configuration. + * @returns The duration in milliseconds, or undefined if the key is not defined. + */ +export const readOptionalHumanDurationInMs = ( + config: Config, + key: string, +): number | undefined => { + const humanDuration = config.has(key) + ? readDurationFromConfig(config, { key }) + : undefined; + + if (!humanDuration) return undefined; + + return durationToMilliseconds(humanDuration); +}; + +/** + * Reads retry configuration options from the provided config object. + * + * @param config - The configuration object to read retry options from, or undefined. + * @returns A RetryOptions object with optional retry settings, or an empty object if config is undefined. + */ +export const readRetryConfig = (config: Config | undefined): RetryOptions => { + if (!config) { + return {}; + } + + return { + maxRetryTime: readOptionalHumanDurationInMs(config, 'maxRetryTime'), + initialRetryTime: readOptionalHumanDurationInMs(config, 'initialRetryTime'), + factor: config.getOptionalNumber('factor'), + multiplier: config.getOptionalNumber('multiplier'), + retries: config.getOptionalNumber('retries'), + }; +}; + +/** + * Reads Kafka configuration from the provided config object. + * + * @param config - The configuration object containing Kafka settings. + * @returns A KafkaConfig object with all necessary Kafka connection and authentication settings. + */ +export const readKafkaConfig = (config: Config): KafkaConfig => { + return { + clientId: config.getString('clientId'), + brokers: config.getStringArray('brokers'), + authenticationTimeout: readOptionalHumanDurationInMs( + config, + 'authenticationTimeout', + ), + connectionTimeout: readOptionalHumanDurationInMs( + config, + 'connectionTimeout', + ), + requestTimeout: readOptionalHumanDurationInMs(config, 'requestTimeout'), + enforceRequestTimeout: config.getOptionalBoolean('enforceRequestTimeout'), + ssl: config.getOptional('ssl') as KafkaConfig['ssl'], + sasl: config.getOptional('sasl') as KafkaConfig['sasl'], + retry: readRetryConfig(config.getOptionalConfig('retry')), + }; +}; diff --git a/plugins/events-backend-module-kafka/src/utils/kafkaTransformers.test.ts b/plugins/events-backend-module-kafka/src/utils/kafkaTransformers.test.ts new file mode 100644 index 0000000000..bfb373ea53 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/utils/kafkaTransformers.test.ts @@ -0,0 +1,137 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { convertHeadersToMetadata, payloadToBuffer } from './kafkaTransformers'; + +describe('kafka-transformers', () => { + describe('convertHeadersToMetadata', () => { + it('should return undefined when headers is undefined', () => { + const result = convertHeadersToMetadata(undefined); + expect(result).toBeUndefined(); + }); + + it('should convert string headers to metadata', () => { + const headers = { + 'content-type': 'application/json', + 'user-id': '12345', + }; + + const result = convertHeadersToMetadata(headers); + + expect(result).toEqual({ + 'content-type': 'application/json', + 'user-id': '12345', + }); + }); + + it('should convert Buffer headers to string metadata', () => { + const headers = { + 'content-type': Buffer.from('application/json'), + 'correlation-id': Buffer.from('abc-123'), + }; + + const result = convertHeadersToMetadata(headers); + + expect(result).toEqual({ + 'content-type': 'application/json', + 'correlation-id': 'abc-123', + }); + }); + + it('should convert array headers to string array metadata', () => { + const headers = { + tags: ['tag1', 'tag2'], + 'buffer-tags': [Buffer.from('tag3'), Buffer.from('tag4')], + 'mixed-tags': ['tag5', Buffer.from('tag6')], + }; + + const result = convertHeadersToMetadata(headers); + + expect(result).toEqual({ + tags: ['tag1', 'tag2'], + 'buffer-tags': ['tag3', 'tag4'], + 'mixed-tags': ['tag5', 'tag6'], + }); + }); + + it('should handle mixed header types', () => { + const headers = { + 'string-header': 'value', + 'buffer-header': Buffer.from('buffer-value'), + 'array-header': ['item1', Buffer.from('item2')], + 'undefined-header': undefined, + }; + + const result = convertHeadersToMetadata(headers); + + expect(result).toEqual({ + 'string-header': 'value', + 'buffer-header': 'buffer-value', + 'array-header': ['item1', 'item2'], + 'undefined-header': undefined, + }); + }); + + it('should handle empty headers object', () => { + const headers = {}; + const result = convertHeadersToMetadata(headers); + expect(result).toEqual({}); + }); + }); + + describe('payloadToBuffer', () => { + it('should return the same Buffer when payload is already a Buffer', () => { + const originalBuffer = Buffer.from('test data'); + + const result = payloadToBuffer(originalBuffer); + + expect(result).toBe(originalBuffer); + expect(Buffer.isBuffer(result)).toBe(true); + }); + + it('should convert string to Buffer', () => { + const payload = 'hello world'; + + const result = payloadToBuffer(payload); + + expect(Buffer.isBuffer(result)).toBe(true); + expect(result.toString()).toBe('hello world'); + }); + + it('should convert object to JSON Buffer', () => { + const payload = { name: 'John', age: 30 }; + + const result = payloadToBuffer(payload); + + expect(Buffer.isBuffer(result)).toBe(true); + expect(JSON.parse(result.toString())).toEqual(payload); + }); + + it('should convert array to JSON Buffer', () => { + const payload = [1, 2, 3, 'test']; + + const result = payloadToBuffer(payload); + + expect(Buffer.isBuffer(result)).toBe(true); + expect(JSON.parse(result.toString())).toEqual(payload); + }); + + it('should convert primitives to JSON Buffer', () => { + expect(payloadToBuffer(42).toString()).toBe('42'); + expect(payloadToBuffer(true).toString()).toBe('true'); + expect(payloadToBuffer(null).toString()).toBe('null'); + }); + }); +}); diff --git a/plugins/events-backend-module-kafka/src/utils/kafkaTransformers.ts b/plugins/events-backend-module-kafka/src/utils/kafkaTransformers.ts new file mode 100644 index 0000000000..871cb08a04 --- /dev/null +++ b/plugins/events-backend-module-kafka/src/utils/kafkaTransformers.ts @@ -0,0 +1,49 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { EventParams } from '@backstage/plugin-events-node'; +import { IHeaders } from 'kafkajs'; + +type EventMetadata = EventParams['metadata']; + +export const convertHeadersToMetadata = ( + headers: IHeaders | undefined, +): EventMetadata => { + if (!headers) return undefined; + + const metadata: EventMetadata = {}; + + Object.entries(headers).forEach(([key, value]) => { + // If value is an array use toString() on all values converting any Buffer types to valid strings + if (Array.isArray(value)) metadata[key] = value.map(v => v.toString()); + // Always return the values using toString() to catch all Buffer types that should be converted to strings + else metadata[key] = value?.toString(); + }); + + return metadata; +}; + +export const payloadToBuffer = (payload: unknown): Buffer => { + if (Buffer.isBuffer(payload)) { + return payload; + } + + if (typeof payload === 'string') { + return Buffer.from(payload, 'utf8'); // More explicit encoding + } + + // Convert to JSON string then encode + return Buffer.from(JSON.stringify(payload), 'utf8'); +}; diff --git a/plugins/events-backend-test-utils/CHANGELOG.md b/plugins/events-backend-test-utils/CHANGELOG.md index a274420041..a9233cc14a 100644 --- a/plugins/events-backend-test-utils/CHANGELOG.md +++ b/plugins/events-backend-test-utils/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-events-backend-test-utils +## 0.1.51 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + +## 0.1.51-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.0 + ## 0.1.50 ### Patch Changes diff --git a/plugins/events-backend-test-utils/package.json b/plugins/events-backend-test-utils/package.json index 7a9b0ab1b0..814fb23241 100644 --- a/plugins/events-backend-test-utils/package.json +++ b/plugins/events-backend-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-test-utils", - "version": "0.1.50", + "version": "0.1.51", "description": "The plugin-events-backend-test-utils for @backstage/plugin-events-node", "backstage": { "role": "node-library", diff --git a/plugins/events-backend/CHANGELOG.md b/plugins/events-backend/CHANGELOG.md index 8bd0eb9a95..125b47b0d3 100644 --- a/plugins/events-backend/CHANGELOG.md +++ b/plugins/events-backend/CHANGELOG.md @@ -1,5 +1,40 @@ # @backstage/plugin-events-backend +## 0.5.9 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.4 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## 0.5.9-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-openapi-utils@0.6.4-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.5.9-next.0 + +### Patch Changes + +- 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 + ## 0.5.8 ### Patch Changes diff --git a/plugins/events-backend/package.json b/plugins/events-backend/package.json index 378e7008f7..6ca5532424 100644 --- a/plugins/events-backend/package.json +++ b/plugins/events-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend", - "version": "0.5.8", + "version": "0.5.9", "backstage": { "role": "backend-plugin", "pluginId": "events", @@ -61,7 +61,7 @@ "@backstage/types": "workspace:^", "@types/express": "^4.17.6", "content-type": "^1.0.5", - "express": "^4.17.1", + "express": "^4.22.0", "express-promise-router": "^4.1.0", "knex": "^3.0.0" }, diff --git a/plugins/events-node/CHANGELOG.md b/plugins/events-node/CHANGELOG.md index 4206c2e726..d748102a2b 100644 --- a/plugins/events-node/CHANGELOG.md +++ b/plugins/events-node/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-events-node +## 0.4.18 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + +## 0.4.18-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## 0.4.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + ## 0.4.17 ### Patch Changes diff --git a/plugins/events-node/package.json b/plugins/events-node/package.json index caa248a1f8..c8dc226198 100644 --- a/plugins/events-node/package.json +++ b/plugins/events-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-node", - "version": "0.4.17", + "version": "0.4.18", "description": "The plugin-events-node module for @backstage/plugin-events-backend", "backstage": { "role": "node-library", @@ -59,13 +59,13 @@ "@types/express": "^4.17.6", "content-type": "^1.0.5", "cross-fetch": "^4.0.0", - "express": "^4.17.1", + "express": "^4.22.0", "uri-template": "^2.0.0" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "msw": "^1.0.0" + "msw": "^2.0.0" }, "configSchema": "config.d.ts" } diff --git a/plugins/events-node/src/api/DefaultEventsService.test.ts b/plugins/events-node/src/api/DefaultEventsService.test.ts index 13a6a68fa4..a62cc24f14 100644 --- a/plugins/events-node/src/api/DefaultEventsService.test.ts +++ b/plugins/events-node/src/api/DefaultEventsService.test.ts @@ -17,7 +17,7 @@ import { DefaultEventsService } from './DefaultEventsService'; import { EventParams } from './EventParams'; import { EVENTS_NOTIFY_TIMEOUT_HEADER } from './EventsService'; -import { rest } from 'msw'; +import { http, HttpResponse } from 'msw'; import { setupServer } from 'msw/node'; import { mockServices, @@ -129,18 +129,18 @@ describe('DefaultEventsService', () => { }); mswServer.use( - rest.put( + http.put( 'http://localhost:0/api/events/bus/v1/subscriptions/a.tester', - (_req, res, ctx) => res(ctx.status(200)), + () => new HttpResponse(null, { status: 200 }), ), - rest.get( + http.get( 'http://localhost:0/api/events/bus/v1/subscriptions/a.tester/events', - (_req, res, ctx) => - res( - ctx.status(200), - ctx.json({ + () => + HttpResponse.json( + { events: [{ topic: 'test', payload: { foo: 'bar' } }], - }), + }, + { status: 200 }, ), ), ); @@ -180,38 +180,36 @@ describe('DefaultEventsService', () => { }); mswServer.use( - rest.put( + http.put( 'http://localhost:0/api/events/bus/v1/subscriptions/a.tester', - (_req, res, ctx) => res(ctx.status(200)), + () => new HttpResponse(null, { status: 200 }), ), // The first and third calls result in a blocking 202 that is resolved after 100ms // The second and fourth calls result in a 200 with an event // The fifth call blocks until the end of the test // No more than 5 calls should be made - rest.get( + http.get( 'http://localhost:0/api/events/bus/v1/subscriptions/a.tester/events', - (_req, res, ctx) => { + () => { callCount += 1; if (callCount === 1 || callCount === 3) { - return res( - ctx.status(202), - ctx.body( - new ReadableStream({ - start(controller) { - setTimeout(() => controller.close(), 100); - }, - }), - ), + return new HttpResponse( + new ReadableStream({ + start(controller) { + setTimeout(() => controller.close(), 100); + }, + }), + { status: 202 }, ); } else if (callCount === 2 || callCount === 4) { - return res( - ctx.status(200), - ctx.json({ + return HttpResponse.json( + { events: [{ topic: 'test', payload: { callCount } }], - }), + }, + { status: 200 }, ); } else if (callCount === 5) { - return res(ctx.status(202), ctx.body(blockingStream)); + return new HttpResponse(blockingStream, { status: 202 }); } throw new Error(`events endpoint called too many times`); }, @@ -268,45 +266,42 @@ describe('DefaultEventsService', () => { }); mswServer.use( - rest.put( + http.put( 'http://localhost:0/api/events/bus/v1/subscriptions/a.tester', - (_req, res, ctx) => res(ctx.status(200)), + () => new HttpResponse(null, { status: 200 }), ), // The first and third calls result in a blocking 202 that is resolved after 100ms // The second and fourth calls result in a 200 with an event // The fifth call blocks until the end of the test // No more than 5 calls should be made - rest.get( + http.get( 'http://localhost:0/api/events/bus/v1/subscriptions/a.tester/events', - (_req, res, ctx) => { + () => { callCount += 1; if (callCount === 1 || callCount === 3) { - return res( - ctx.status(202), - ctx.body( - new ReadableStream({ - start(controller) { - setTimeout(() => controller.close(), 100); - }, - }), - ), + return new HttpResponse( + new ReadableStream({ + start(controller) { + setTimeout(() => controller.close(), 100); + }, + }), + { status: 202 }, ); } else if (callCount === 2 || callCount === 4) { - return res( - ctx.status(200), - ctx.json({ + return HttpResponse.json( + { events: [{ topic: 'test', payload: { callCount } }], - }), + }, + { status: 200 }, ); } else if (callCount === 5) { // 5th call has a timeout header so polling should proceed to the next call - return res( - ctx.set(EVENTS_NOTIFY_TIMEOUT_HEADER, '100'), - ctx.status(202), - ctx.body(blockingStream), - ); + return new HttpResponse(blockingStream, { + status: 202, + headers: { [EVENTS_NOTIFY_TIMEOUT_HEADER]: '100' }, + }); } else if (callCount === 6) { - return res(ctx.status(202), ctx.body(blockingStream)); + return new HttpResponse(blockingStream, { status: 202 }); } throw new Error(`events endpoint called too many times`); }, @@ -358,11 +353,11 @@ describe('DefaultEventsService', () => { let calledApi = false; mswServer.use( - rest.put( + http.put( 'http://localhost:0/api/events/bus/v1/subscriptions/a.tester', - (_req, res, ctx) => { + () => { calledApi = true; - res(ctx.status(200)); + return new HttpResponse(null, { status: 200 }); }, ), ); @@ -393,9 +388,9 @@ describe('DefaultEventsService', () => { }); mswServer.use( - rest.put( + http.put( 'http://localhost:0/api/events/bus/v1/subscriptions/a.tester', - (_req, res, ctx) => res(ctx.status(404)), + () => new HttpResponse(null, { status: 404 }), ), ); @@ -432,9 +427,9 @@ describe('DefaultEventsService', () => { }); mswServer.use( - rest.put( + http.put( 'http://localhost:0/api/events/bus/v1/subscriptions/a.tester', - (_req, res, ctx) => res(ctx.status(404)), + () => new HttpResponse(null, { status: 404 }), ), ); diff --git a/plugins/example-todo-list-backend/CHANGELOG.md b/plugins/example-todo-list-backend/CHANGELOG.md index 4e403ec772..4d7a89509f 100644 --- a/plugins/example-todo-list-backend/CHANGELOG.md +++ b/plugins/example-todo-list-backend/CHANGELOG.md @@ -1,5 +1,28 @@ # @internal/plugin-todo-list-backend +## 1.0.46 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + +## 1.0.46-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/errors@1.2.7 + +## 1.0.46-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + - @backstage/errors@1.2.7 + ## 1.0.45 ### Patch Changes diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json index a9877eb25c..108655947a 100644 --- a/plugins/example-todo-list-backend/package.json +++ b/plugins/example-todo-list-backend/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list-backend", - "version": "1.0.45", + "version": "1.0.46", "backstage": { "role": "backend-plugin", "pluginId": "todo-list", @@ -41,7 +41,7 @@ "@backstage/backend-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", "@types/express": "^4.17.6", - "express": "^4.17.1", + "express": "^4.22.0", "express-promise-router": "^4.1.0", "uuid": "^11.0.0" }, diff --git a/plugins/example-todo-list/CHANGELOG.md b/plugins/example-todo-list/CHANGELOG.md index 8a5fc0d9d4..798fa8df02 100644 --- a/plugins/example-todo-list/CHANGELOG.md +++ b/plugins/example-todo-list/CHANGELOG.md @@ -1,5 +1,21 @@ # @internal/plugin-todo-list +## 1.0.46 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + +## 1.0.46-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/core-components@0.18.4-next.0 + ## 1.0.45 ### Patch Changes diff --git a/plugins/example-todo-list/package.json b/plugins/example-todo-list/package.json index e9d9d94376..ad138b4713 100644 --- a/plugins/example-todo-list/package.json +++ b/plugins/example-todo-list/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list", - "version": "1.0.45", + "version": "1.0.46", "backstage": { "role": "frontend-plugin", "pluginId": "todo-list", diff --git a/plugins/gateway-backend/CHANGELOG.md b/plugins/gateway-backend/CHANGELOG.md index ecf45e21ae..5e6c6254d9 100644 --- a/plugins/gateway-backend/CHANGELOG.md +++ b/plugins/gateway-backend/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-gateway-backend +## 1.1.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 7e860dd: Updated dependency `eventsource` to `^4.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + +## 1.1.1-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 1.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + ## 1.1.0 ### Minor Changes diff --git a/plugins/gateway-backend/package.json b/plugins/gateway-backend/package.json index 39887a0857..4cc39faf7b 100644 --- a/plugins/gateway-backend/package.json +++ b/plugins/gateway-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-gateway-backend", - "version": "1.1.0", + "version": "1.1.1", "backstage": { "role": "backend-plugin", "pluginId": "gateway", @@ -37,7 +37,7 @@ "@backstage/backend-plugin-api": "workspace:^", "@opentelemetry/api": "^1.9.0", "@opentelemetry/core": "^1.29.0", - "express": "^4.17.1", + "express": "^4.22.0", "http-proxy-middleware": "^3.0.3" }, "devDependencies": { @@ -46,7 +46,7 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@types/express": "^4.17.6", - "eventsource": "^3.0.6", + "eventsource": "^4.0.0", "wait-for-expect": "^3.0.2" } } diff --git a/plugins/home-react/CHANGELOG.md b/plugins/home-react/CHANGELOG.md index 10ee11c83e..81c9fbc1be 100644 --- a/plugins/home-react/CHANGELOG.md +++ b/plugins/home-react/CHANGELOG.md @@ -1,5 +1,40 @@ # @backstage/plugin-home-react +## 0.1.33 + +### Patch Changes + +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + +## 0.1.33-next.1 + +### Patch Changes + +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/core-components@0.18.4-next.2 + - @backstage/core-plugin-api@1.12.1-next.0 + +## 0.1.33-next.0 + +### Patch Changes + +- 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 + ## 0.1.32 ### Patch Changes diff --git a/plugins/home-react/package.json b/plugins/home-react/package.json index 3a2840a1e6..8f69f7eab7 100644 --- a/plugins/home-react/package.json +++ b/plugins/home-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home-react", - "version": "0.1.32", + "version": "0.1.33", "description": "A Backstage plugin that contains react components helps you build a home page", "backstage": { "role": "web-library", @@ -60,7 +60,7 @@ "@backstage/frontend-plugin-api": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", - "@rjsf/utils": "5.23.2" + "@rjsf/utils": "5.24.13" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/plugins/home/CHANGELOG.md b/plugins/home/CHANGELOG.md index 044894a4ad..6a1cbcd825 100644 --- a/plugins/home/CHANGELOG.md +++ b/plugins/home/CHANGELOG.md @@ -1,5 +1,60 @@ # @backstage/plugin-home +## 0.8.15 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/plugin-home-react@0.1.33 + - @backstage/theme@0.7.1 + +## 0.8.15-next.1 + +### Patch Changes + +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- Updated dependencies + - @backstage/core-app-api@1.19.3-next.1 + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/plugin-home-react@0.1.33-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + +## 0.8.15-next.0 + +### Patch Changes + +- 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 + ## 0.8.14 ### Patch Changes diff --git a/plugins/home/package.json b/plugins/home/package.json index 4bc9361884..5716626c36 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home", - "version": "0.8.14", + "version": "0.8.15", "description": "A Backstage plugin that helps you build a home page", "backstage": { "role": "frontend-plugin", @@ -60,7 +60,6 @@ "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/core-app-api": "workspace:^", - "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", @@ -70,10 +69,10 @@ "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", - "@rjsf/core": "5.23.2", - "@rjsf/material-ui": "5.23.2", - "@rjsf/utils": "5.23.2", - "@rjsf/validator-ajv8": "5.23.2", + "@rjsf/core": "5.24.13", + "@rjsf/material-ui": "5.24.13", + "@rjsf/utils": "5.24.13", + "@rjsf/validator-ajv8": "5.24.13", "lodash": "^4.17.21", "luxon": "^3.4.3", "react-grid-layout": "1.3.4", diff --git a/plugins/home/report.api.md b/plugins/home/report.api.md index 7036e9d6d2..7b15ecd8a7 100644 --- a/plugins/home/report.api.md +++ b/plugins/home/report.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 { ApiRef } from '@backstage/core-plugin-api'; +import { ApiRef } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { CardConfig as CardConfig_2 } from '@backstage/plugin-home-react'; import { CardExtensionProps as CardExtensionProps_2 } from '@backstage/plugin-home-react'; diff --git a/plugins/home/src/alpha.tsx b/plugins/home/src/alpha.tsx index a92316c7f0..6cad71d643 100644 --- a/plugins/home/src/alpha.tsx +++ b/plugins/home/src/alpha.tsx @@ -26,7 +26,6 @@ import { storageApiRef, ApiBlueprint, } from '@backstage/frontend-plugin-api'; -import { compatWrapper } from '@backstage/core-compat-api'; import { VisitListener } from './components/'; import { visitsApiRef, VisitsStorageApi } from './api'; @@ -57,14 +56,12 @@ const homePage = PageBlueprint.makeWithOverrides({ path: '/home', routeRef: rootRouteRef, loader: () => - import('./components/').then(m => - compatWrapper( - , - ), - ), + import('./components/').then(m => ( + + )), }); }, }); diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index 22850c7d82..a8ebfa6f8f 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,78 @@ # @backstage/plugin-kubernetes-backend +## 0.21.0 + +### Minor Changes + +- 7f9846f: Add possibility to extends Kubernetes REST API. Add fetcher to parameters for custom objects provider + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- fb029b6: Updated luxon types +- e9589d9: Replace `@aws-sdk/signature-v4` with `@smithy/signature-v4`, + as stated in the [package documentation](https://www.npmjs.com/package/@aws-sdk/signature-v4?activeTab=readme) +- 8fa8d87: Add Kubernetes Plugin Secrets Accordion with masked secret datas +- Updated dependencies + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-kubernetes-node@0.4.0 + - @backstage/plugin-kubernetes-common@0.9.9 + - @backstage/plugin-catalog-node@1.20.1 + +## 0.21.0-next.2 + +### Minor Changes + +- 7f9846f: Add possibility to extends Kubernetes REST API. Add fetcher to parameters for custom objects provider + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- fb029b6: Updated luxon types +- Updated dependencies + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/plugin-kubernetes-node@0.4.0-next.2 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.19 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + - @backstage/plugin-permission-common@0.9.3 + +## 0.20.5-next.1 + +### Patch Changes + +- 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 + +## 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 + ## 0.20.4 ### Patch Changes diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index 1280a17491..2aac84c616 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-backend", - "version": "0.20.4", + "version": "0.21.0", "description": "A Backstage backend plugin that integrates towards Kubernetes", "backstage": { "role": "backend-plugin", @@ -63,7 +63,7 @@ "@kubernetes/client-node": "1.4.0", "@smithy/signature-v4": "^4.1.0", "@types/http-proxy-middleware": "^1.0.0", - "express": "^4.17.1", + "express": "^4.22.0", "express-promise-router": "^4.1.0", "fs-extra": "^11.2.0", "http-proxy-middleware": "^2.0.6", @@ -78,7 +78,6 @@ "@backstage/plugin-permission-backend": "workspace:^", "@backstage/plugin-permission-backend-module-allow-all-policy": "workspace:^", "@types/express": "^4.17.6", - "@types/luxon": "^3.0.0", "msw": "^1.0.0", "supertest": "^7.0.0", "ws": "^8.18.0" diff --git a/plugins/kubernetes-backend/src/cluster-locator/__snapshots__/CatalogClusterLocator.test.ts.snap b/plugins/kubernetes-backend/src/cluster-locator/__snapshots__/CatalogClusterLocator.test.ts.snap index f8b3e71515..64526faacb 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/__snapshots__/CatalogClusterLocator.test.ts.snap +++ b/plugins/kubernetes-backend/src/cluster-locator/__snapshots__/CatalogClusterLocator.test.ts.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`CatalogClusterLocator returns the aws cluster details provided by annotations 1`] = ` { diff --git a/plugins/kubernetes-backend/src/plugin.ts b/plugins/kubernetes-backend/src/plugin.ts index b4283d4914..5a2a22f2b5 100644 --- a/plugins/kubernetes-backend/src/plugin.ts +++ b/plugins/kubernetes-backend/src/plugin.ts @@ -36,6 +36,9 @@ import { kubernetesObjectsProviderExtensionPoint, type KubernetesObjectsProviderExtensionPoint, KubernetesObjectsProviderFactory, + KubernetesRouterExtensionPoint, + kubernetesRouterExtensionPoint, + KubernetesRouterFactory, type KubernetesServiceLocator, kubernetesServiceLocatorExtensionPoint, type KubernetesServiceLocatorExtensionPoint, @@ -157,6 +160,24 @@ class AuthStrategy implements KubernetesAuthStrategyExtensionPoint { } } +class CustomRouter implements KubernetesRouterExtensionPoint { + private router: KubernetesRouterFactory | undefined; + + getRouter() { + return this.router; + } + + addRouter(router: KubernetesRouterFactory) { + if (this.router) { + throw new Error( + 'Multiple Kubernetes routers is not supported at this time', + ); + } + + this.router = router; + } +} + /** * This is the backend plugin that provides the Kubernetes integration. * @public @@ -169,6 +190,7 @@ export const kubernetesPlugin = createBackendPlugin({ const extPointAuthStrategy = new AuthStrategy(); const extPointFetcher = new Fetcher(); const extPointServiceLocator = new ServiceLocator(); + const extPointRouter = new CustomRouter(); env.registerExtensionPoint( kubernetesObjectsProviderExtensionPoint, @@ -190,6 +212,7 @@ export const kubernetesPlugin = createBackendPlugin({ kubernetesServiceLocatorExtensionPoint, extPointServiceLocator, ); + env.registerExtensionPoint(kubernetesRouterExtensionPoint, extPointRouter); env.registerInit({ deps: { @@ -247,6 +270,7 @@ export const kubernetesPlugin = createBackendPlugin({ clusterSupplier, serviceLocator, objectsProvider, + customRouter: extPointRouter.getRouter(), }); http.use(await router.getRouter()); diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts index e525c74077..fbacadd2a2 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts @@ -1263,5 +1263,160 @@ describe('KubernetesFetcher', () => { ]); expect(result.responses).toMatchObject(POD_METRICS_FIXTURE); }); + it('should mask secret data values with ***', async () => { + worker.use( + rest.get('http://localhost:9999/api/v1/secrets', (req, res, ctx) => + res( + checkToken(req, ctx, 'token'), + withLabels(req, ctx, { + items: [ + { + metadata: { name: 'secret-name' }, + data: { + username: 'dXNlcm5hbWU=', + password: 'cGFzc3dvcmQ=', + apiKey: 'YXBpS2V5', + }, + }, + ], + }), + ), + ), + ); + + const result = await sut.fetchObjectsForService({ + serviceId: 'some-service', + clusterDetails: { + name: 'cluster1', + url: 'http://localhost:9999', + authMetadata: {}, + }, + credential: { type: 'bearer token', token: 'token' }, + objectTypesToFetch: new Set([ + { + group: '', + apiVersion: 'v1', + plural: 'secrets', + objectType: 'secrets', + }, + ]), + labelSelector: '', + customResources: [], + }); + + expect(result).toStrictEqual({ + errors: [], + responses: [ + { + type: 'secrets', + resources: [ + { + metadata: { + name: 'secret-name', + labels: {}, + }, + data: { + username: '***', + password: '***', + apiKey: '***', + }, + }, + ], + }, + ], + }); + }); + + it('should handle secrets without data field', async () => { + worker.use( + rest.get('http://localhost:9999/api/v1/secrets', (req, res, ctx) => + res( + checkToken(req, ctx, 'token'), + withLabels(req, ctx, { + items: [ + { + metadata: { name: 'secret-without-data' }, + }, + ], + }), + ), + ), + ); + + const result = await sut.fetchObjectsForService({ + serviceId: 'some-service', + clusterDetails: { + name: 'cluster1', + url: 'http://localhost:9999', + authMetadata: {}, + }, + credential: { type: 'bearer token', token: 'token' }, + objectTypesToFetch: new Set([ + { + group: '', + apiVersion: 'v1', + plural: 'secrets', + objectType: 'secrets', + }, + ]), + labelSelector: '', + customResources: [], + }); + + expect(result).toStrictEqual({ + errors: [], + responses: [ + { + type: 'secrets', + resources: [ + { + metadata: { + name: 'secret-without-data', + labels: {}, + }, + }, + ], + }, + ], + }); + }); + }); + + describe('transformResources', () => { + let fetcher: KubernetesClientBasedFetcher; + + beforeEach(() => { + fetcher = new KubernetesClientBasedFetcher({ + logger: mockServices.logger.mock(), + }); + }); + + it('removes List suffix from custom resource kinds', () => { + const result = (fetcher as any).transformResources( + 'customresources', + 'HelloWorldList', + [{ name: 'foo' }], + ); + expect(result[0].kind).toBe('HelloWorld'); + }); + + it('masks secret data values', () => { + const result = (fetcher as any).transformResources( + 'secrets', + 'SecretList', + [{ data: { password: 'secret123' } }], + ); + expect(result[0].data.password).toBe('***'); + }); + + it('returns other types unchanged', () => { + const items = [{ name: 'pod' }]; + const result = (fetcher as any).transformResources( + 'pods', + 'PodList', + items, + ); + expect(result).toBe(items); + }); }); }); diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts index 4b9b73e488..e89b86a8e0 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts @@ -103,13 +103,7 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { ? r.json().then( ({ kind, items }): FetchResponse => ({ type: objectType, - resources: - objectType === 'customresources' - ? items.map((item: JsonObject) => ({ - ...item, - kind: kind.replace(/(List)$/, ''), - })) - : items, + resources: this.transformResources(objectType, kind, items), }), ) : this.handleUnsuccessfulResponse(params.clusterDetails.name, r), @@ -320,4 +314,33 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { } return [url, requestInit]; } + + private transformResources( + objectType: string, + kind: string, + items: JsonObject[], + ): JsonObject[] { + if (objectType === 'customresources') { + return items.map((item: JsonObject) => ({ + ...item, + kind: kind.replace(/(List)$/, ''), + })); + } + + if (objectType === 'secrets') { + return items.map((item: JsonObject) => { + if (item.data && typeof item.data === 'object') { + return { + ...item, + data: Object.fromEntries( + Object.keys(item.data).map(key => [key, '***']), + ), + }; + } + return item; + }); + } + + return items; + } } diff --git a/plugins/kubernetes-backend/src/service/KubernetesInitializer.ts b/plugins/kubernetes-backend/src/service/KubernetesInitializer.ts index 46ce5fa888..a12f291ba2 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesInitializer.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesInitializer.ts @@ -209,6 +209,7 @@ export class KubernetesInitializer { customResources, objectTypesToFetch, }), + fetcher, clusterSupplier, serviceLocator, customResources, diff --git a/plugins/kubernetes-backend/src/service/KubernetesRouter.test.ts b/plugins/kubernetes-backend/src/service/KubernetesRouter.test.ts index 4157cf9444..eb643704c0 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesRouter.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesRouter.test.ts @@ -28,6 +28,7 @@ import { KubernetesFetcher, KubernetesServiceLocator, KubernetesCredential, + kubernetesRouterExtensionPoint, } from '@backstage/plugin-kubernetes-node'; import { HEADER_KUBERNETES_CLUSTER, @@ -809,4 +810,39 @@ metadata: 'Unsupported kubernetes.serviceLocatorMethod "unsupported"', ); }); + + it('custom router', async () => { + const { server } = await startTestBackend({ + features: [ + minimalValidConfigService, + import('@backstage/plugin-kubernetes-backend'), + createBackendModule({ + pluginId: 'kubernetes', + moduleId: 'testRouter', + register(env) { + env.registerInit({ + deps: { extension: kubernetesRouterExtensionPoint }, + async init({ extension }) { + extension.addRouter(({ getDefault }) => { + const router = getDefault(); + + router.get('/test', (_req, res) => { + res.json({ status: 'ok' }); + }); + + return router; + }); + }, + }); + }, + }), + ], + }); + app = server; + + const response = await request(app).get('/api/kubernetes/test'); + + expect(response.body).toEqual({ status: 'ok' }); + expect(response.status).toEqual(200); + }); }); diff --git a/plugins/kubernetes-backend/src/service/KubernetesRouter.ts b/plugins/kubernetes-backend/src/service/KubernetesRouter.ts index 589e75a172..48842f562c 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesRouter.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesRouter.ts @@ -41,6 +41,7 @@ import { KubernetesClustersSupplier, KubernetesFetcher, KubernetesObjectsProvider, + KubernetesRouterFactory, KubernetesServiceLocator, } from '@backstage/plugin-kubernetes-node'; import { addResourceRoutesToRouter } from '../routes/resourcesRoutes'; @@ -62,6 +63,7 @@ export interface KubernetesEnvironment { clusterSupplier: KubernetesClustersSupplier; serviceLocator: KubernetesServiceLocator; objectsProvider: KubernetesObjectsProvider; + customRouter?: KubernetesRouterFactory; } export class KubernetesRouter { @@ -86,6 +88,7 @@ export class KubernetesRouter { catalog, discovery, httpAuth, + customRouter, } = this.env; logger.info('Initializing Kubernetes backend'); @@ -108,14 +111,31 @@ export class KubernetesRouter { authStrategyMap, ); - return this.buildRouter( - objectsProvider, - clusterSupplier, - catalog, - proxy, - permissions, - httpAuth, - authStrategyMap, + return ( + customRouter?.({ + getDefault: () => + this.buildDefaultRouter( + objectsProvider, + clusterSupplier, + catalog, + proxy, + permissions, + httpAuth, + authStrategyMap, + ), + objectsProvider, + clusterSupplier, + authStrategyMap, + }) ?? + this.buildDefaultRouter( + objectsProvider, + clusterSupplier, + catalog, + proxy, + permissions, + httpAuth, + authStrategyMap, + ) ); } @@ -138,7 +158,7 @@ export class KubernetesRouter { }); } - private buildRouter( + private buildDefaultRouter( objectsProvider: KubernetesObjectsProvider, clusterSupplier: KubernetesClustersSupplier, catalog: CatalogService, diff --git a/plugins/kubernetes-cluster/CHANGELOG.md b/plugins/kubernetes-cluster/CHANGELOG.md index 22a87cbb88..5666354a62 100644 --- a/plugins/kubernetes-cluster/CHANGELOG.md +++ b/plugins/kubernetes-cluster/CHANGELOG.md @@ -1,5 +1,55 @@ # @backstage/plugin-kubernetes-cluster +## 0.0.32 + +### Patch Changes + +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/plugin-kubernetes-react@0.5.14 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-kubernetes-common@0.9.9 + - @backstage/core-plugin-api@1.12.1 + - @backstage/plugin-permission-react@0.4.39 + +## 0.0.32-next.2 + +### Patch Changes + +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-model@1.7.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + - @backstage/plugin-kubernetes-react@0.5.14-next.1 + - @backstage/plugin-permission-react@0.4.39-next.0 + +## 0.0.32-next.1 + +### Patch Changes + +- 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 + +## 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 + ## 0.0.31 ### Patch Changes diff --git a/plugins/kubernetes-cluster/package.json b/plugins/kubernetes-cluster/package.json index 519eabd6ff..4e43179c0c 100644 --- a/plugins/kubernetes-cluster/package.json +++ b/plugins/kubernetes-cluster/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-cluster", - "version": "0.0.31", + "version": "0.0.32", "description": "A Backstage plugin that shows details of Kubernetes clusters", "backstage": { "role": "frontend-plugin", @@ -72,7 +72,7 @@ "@backstage/test-utils": "workspace:^", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^16.0.0", - "@types/node": "^20.16.0", + "@types/node": "^22.13.14", "@types/react": "^18.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", diff --git a/plugins/kubernetes-cluster/report-alpha.api.md b/plugins/kubernetes-cluster/report-alpha.api.md index 13314d6c83..2ce9c33c47 100644 --- a/plugins/kubernetes-cluster/report-alpha.api.md +++ b/plugins/kubernetes-cluster/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 kubernetesClusterTranslationRef: TranslationRef< diff --git a/plugins/kubernetes-common/CHANGELOG.md b/plugins/kubernetes-common/CHANGELOG.md index effad6fbff..de8f93897c 100644 --- a/plugins/kubernetes-common/CHANGELOG.md +++ b/plugins/kubernetes-common/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-kubernetes-common +## 0.9.9 + +### Patch Changes + +- 8fa8d87: Add Kubernetes Plugin Secrets Accordion with masked secret datas + +## 0.9.9-next.0 + +### Patch Changes + +- 8fa8d87: Add Kubernetes Plugin Secrets Accordion with masked secret datas + ## 0.9.8 ### Patch Changes diff --git a/plugins/kubernetes-common/package.json b/plugins/kubernetes-common/package.json index 0a61221683..db2d9c2837 100644 --- a/plugins/kubernetes-common/package.json +++ b/plugins/kubernetes-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-common", - "version": "0.9.8", + "version": "0.9.9", "description": "Common functionalities for kubernetes, to be shared between kubernetes and kubernetes-backend plugin", "backstage": { "role": "common-library", diff --git a/plugins/kubernetes-common/report.api.md b/plugins/kubernetes-common/report.api.md index 748ae94656..bf99cdaddd 100644 --- a/plugins/kubernetes-common/report.api.md +++ b/plugins/kubernetes-common/report.api.md @@ -254,6 +254,7 @@ export type FetchResponse = | PodFetchResponse | ServiceFetchResponse | ConfigMapFetchResponse + | SecretFetchResponse | DeploymentFetchResponse | LimitRangeFetchResponse | ResourceQuotaFetchResponse @@ -283,6 +284,8 @@ export interface GroupedResponses extends DeploymentResources { // (undocumented) jobs: V1Job[]; // (undocumented) + secrets: V1Secret[]; + // (undocumented) services: V1Service[]; // (undocumented) statefulsets: V1StatefulSet[]; @@ -447,6 +450,14 @@ export interface ResourceRef { namespace: string; } +// @public (undocumented) +export interface SecretFetchResponse { + // (undocumented) + resources: Array; + // (undocumented) + type: 'secrets'; +} + // @public (undocumented) export interface SecretsFetchResponse { // (undocumented) diff --git a/plugins/kubernetes-common/src/types.ts b/plugins/kubernetes-common/src/types.ts index 8ce3d80409..30846e3b8e 100644 --- a/plugins/kubernetes-common/src/types.ts +++ b/plugins/kubernetes-common/src/types.ts @@ -129,6 +129,7 @@ export type FetchResponse = | PodFetchResponse | ServiceFetchResponse | ConfigMapFetchResponse + | SecretFetchResponse | DeploymentFetchResponse | LimitRangeFetchResponse | ResourceQuotaFetchResponse @@ -161,6 +162,12 @@ export interface ConfigMapFetchResponse { resources: Array; } +/** @public */ +export interface SecretFetchResponse { + type: 'secrets'; + resources: Array; +} + /** @public */ export interface DeploymentFetchResponse { type: 'deployments'; @@ -297,6 +304,7 @@ export interface DeploymentResources { export interface GroupedResponses extends DeploymentResources { services: V1Service[]; configMaps: V1ConfigMap[]; + secrets: V1Secret[]; ingresses: V1Ingress[]; jobs: V1Job[]; cronJobs: V1CronJob[]; diff --git a/plugins/kubernetes-common/src/util/response.ts b/plugins/kubernetes-common/src/util/response.ts index b62fa162d1..ba181c48e2 100644 --- a/plugins/kubernetes-common/src/util/response.ts +++ b/plugins/kubernetes-common/src/util/response.ts @@ -40,6 +40,9 @@ export const groupResponses = ( case 'configmaps': prev.configMaps.push(...next.resources); break; + case 'secrets': + prev.secrets.push(...next.resources); + break; case 'horizontalpodautoscalers': prev.horizontalPodAutoscalers.push(...next.resources); break; @@ -71,6 +74,7 @@ export const groupResponses = ( deployments: [], services: [], configMaps: [], + secrets: [], horizontalPodAutoscalers: [], ingresses: [], jobs: [], diff --git a/plugins/kubernetes-node/CHANGELOG.md b/plugins/kubernetes-node/CHANGELOG.md index 186fad2d0b..a0fad104d8 100644 --- a/plugins/kubernetes-node/CHANGELOG.md +++ b/plugins/kubernetes-node/CHANGELOG.md @@ -1,5 +1,48 @@ # @backstage/plugin-kubernetes-node +## 0.4.0 + +### Minor Changes + +- 7f9846f: Add possibility to extends Kubernetes REST API. Add fetcher to parameters for custom objects provider + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-kubernetes-common@0.9.9 + +## 0.4.0-next.2 + +### Minor Changes + +- 7f9846f: Add possibility to extends Kubernetes REST API. Add fetcher to parameters for custom objects provider + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/types@1.2.2 + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + +## 0.3.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.9.9-next.0 + +## 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 + ## 0.3.6 ### Patch Changes diff --git a/plugins/kubernetes-node/package.json b/plugins/kubernetes-node/package.json index cadd27eabf..8778449eaa 100644 --- a/plugins/kubernetes-node/package.json +++ b/plugins/kubernetes-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-node", - "version": "0.3.6", + "version": "0.4.0", "description": "Node.js library for the kubernetes plugin", "backstage": { "role": "node-library", @@ -43,6 +43,7 @@ "@backstage/plugin-kubernetes-common": "workspace:^", "@backstage/types": "workspace:^", "@kubernetes/client-node": "1.4.0", + "@types/express": "^4.17.6", "node-fetch": "^2.7.0", "winston": "^3.2.1" }, diff --git a/plugins/kubernetes-node/report.api.md b/plugins/kubernetes-node/report.api.md index 2e96057be8..c23a0b5bb8 100644 --- a/plugins/kubernetes-node/report.api.md +++ b/plugins/kubernetes-node/report.api.md @@ -8,6 +8,7 @@ import { BackstageCredentials } from '@backstage/backend-plugin-api'; import { CustomResource as CustomResource_2 } from '@backstage/plugin-kubernetes-node'; import { CustomResourceMatcher } from '@backstage/plugin-kubernetes-common'; import { Entity } from '@backstage/catalog-model'; +import type express from 'express'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { FetchResponse } from '@backstage/plugin-kubernetes-common'; import { JsonObject } from '@backstage/types'; @@ -198,6 +199,7 @@ export type KubernetesObjectsProviderFactory = (opts: { getDefault: () => Promise; clusterSupplier: KubernetesClustersSupplier_2; serviceLocator: KubernetesServiceLocator_2; + fetcher: KubernetesFetcher_2; customResources: CustomResource_2[]; objectTypesToFetch?: ObjectToFetch_2[]; authStrategy: AuthenticationStrategy_2; @@ -221,6 +223,25 @@ export type KubernetesObjectTypes = | 'daemonsets' | 'secrets'; +// @public +export interface KubernetesRouterExtensionPoint { + // (undocumented) + addRouter(router: KubernetesRouterFactory): void; +} + +// @public +export const kubernetesRouterExtensionPoint: ExtensionPoint; + +// @public +export type KubernetesRouterFactory = (opts: { + getDefault: () => express.Router; + objectsProvider: KubernetesObjectsProvider_2; + clusterSupplier: KubernetesClustersSupplier_2; + authStrategyMap: { + [key: string]: AuthenticationStrategy_2; + }; +}) => express.Router; + // @public export interface KubernetesServiceLocator { // (undocumented) diff --git a/plugins/kubernetes-node/src/extensions.ts b/plugins/kubernetes-node/src/extensions.ts index 6f112ece23..50b529d05b 100644 --- a/plugins/kubernetes-node/src/extensions.ts +++ b/plugins/kubernetes-node/src/extensions.ts @@ -23,6 +23,7 @@ import { KubernetesObjectsProvider, KubernetesServiceLocator, } from '@backstage/plugin-kubernetes-node'; +import type express from 'express'; /** * A factory function for creating a KubernetesObjectsProvider. @@ -33,6 +34,7 @@ export type KubernetesObjectsProviderFactory = (opts: { getDefault: () => Promise; clusterSupplier: KubernetesClustersSupplier; serviceLocator: KubernetesServiceLocator; + fetcher: KubernetesFetcher; customResources: CustomResource[]; objectTypesToFetch?: ObjectToFetch[]; authStrategy: AuthenticationStrategy; @@ -168,3 +170,34 @@ export const kubernetesServiceLocatorExtensionPoint = createExtensionPoint({ id: 'kubernetes.service-locator', }); + +/** + * A factory function for creating a kubernetes router. + * + * @public + */ +export type KubernetesRouterFactory = (opts: { + getDefault: () => express.Router; + objectsProvider: KubernetesObjectsProvider; + clusterSupplier: KubernetesClustersSupplier; + authStrategyMap: { [key: string]: AuthenticationStrategy }; +}) => express.Router; + +/** + * The interface for {@link kubernetesRouterExtensionPoint}. + * + * @public + */ +export interface KubernetesRouterExtensionPoint { + addRouter(router: KubernetesRouterFactory): void; +} + +/** + * An extension point the exposes the ability to configure a kubernetes service locator. + * + * @public + */ +export const kubernetesRouterExtensionPoint = + createExtensionPoint({ + id: 'kubernetes.router', + }); diff --git a/plugins/kubernetes-react/CHANGELOG.md b/plugins/kubernetes-react/CHANGELOG.md index 0df51a9af2..d1bb328016 100644 --- a/plugins/kubernetes-react/CHANGELOG.md +++ b/plugins/kubernetes-react/CHANGELOG.md @@ -1,5 +1,40 @@ # @backstage/plugin-kubernetes-react +## 0.5.14 + +### Patch Changes + +- f966a85: Enabled a pod terminal at GKE +- 8fa8d87: Add Kubernetes Plugin Secrets Accordion with masked secret datas +- f15d5f1: add missing i18n support for kubernetes and kubernetes-react +- Updated dependencies + - @backstage/core-components@0.18.4 + - @backstage/plugin-kubernetes-common@0.9.9 + - @backstage/core-plugin-api@1.12.1 + +## 0.5.14-next.1 + +### Patch Changes + +- 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 + +## 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 + ## 0.5.13 ### Patch Changes diff --git a/plugins/kubernetes-react/package.json b/plugins/kubernetes-react/package.json index e22a41d1ec..a634594e60 100644 --- a/plugins/kubernetes-react/package.json +++ b/plugins/kubernetes-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-react", - "version": "0.5.13", + "version": "0.5.14", "description": "Web library for the kubernetes-react plugin", "backstage": { "role": "web-library", @@ -84,7 +84,7 @@ "@testing-library/react": "^16.0.0", "@types/react": "^18.0.0", "jest-websocket-mock": "^2.5.0", - "msw": "^1.3.1", + "msw": "^2.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", "react-router-dom": "^6.3.0" diff --git a/plugins/kubernetes-react/report-alpha.api.md b/plugins/kubernetes-react/report-alpha.api.md index 00a8b6b819..1c8746eb8f 100644 --- a/plugins/kubernetes-react/report-alpha.api.md +++ b/plugins/kubernetes-react/report-alpha.api.md @@ -3,13 +3,90 @@ > 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 kubernetesReactTranslationRef: TranslationRef< 'kubernetes-react', { + readonly 'namespace.label': 'namespace:'; + readonly 'namespace.labelWithValue': 'namespace: {{namespace}}'; + readonly 'events.noEventsFound': 'No events found'; + readonly 'events.eventTooltip': '{{eventType}} event'; + readonly 'events.firstEvent': 'First event {{timeAgo}} (count: {{count}})'; + readonly 'cluster.label': 'Cluster'; + readonly 'cluster.pods': 'pods'; + readonly 'cluster.pods_one': '{{count}} pod'; + readonly 'cluster.pods_other': '{{count}} pods'; + readonly 'cluster.podsWithErrors': 'pods with errors'; + readonly 'cluster.podsWithErrors_one': '{{count}} pod with errors'; + readonly 'cluster.podsWithErrors_other': '{{count}} pods with errors'; + readonly 'cluster.noPodsWithErrors': 'No pods with errors'; + readonly 'errorPanel.message': 'There was a problem retrieving some Kubernetes resources for the entity: {{entityName}}. This could mean that the Error Reporting card is not completely accurate.'; + readonly 'errorPanel.title': 'There was a problem retrieving Kubernetes objects'; + readonly 'errorPanel.errorsLabel': 'Errors'; + readonly 'errorPanel.clusterLabel': 'Cluster'; + readonly 'errorPanel.clusterLabelValue': 'Cluster: {{cluster}}'; + readonly 'errorPanel.fetchError': 'Error communicating with Kubernetes: {{errorType}}, message: {{message}}'; + readonly 'errorPanel.resourceError': "Error fetching Kubernetes resource: '{{resourcePath}}', error: {{errorType}}, status code: {{statusCode}}"; + readonly 'pods.pods_one': '{{count}} pod'; + readonly 'pods.pods_other': '{{count}} pods'; + readonly 'podsTable.columns.name': 'name'; + readonly 'podsTable.columns.id': 'ID'; + readonly 'podsTable.columns.status': 'status'; + readonly 'podsTable.columns.phase': 'phase'; + readonly 'podsTable.columns.containersReady': 'containers ready'; + readonly 'podsTable.columns.totalRestarts': 'total restarts'; + readonly 'podsTable.columns.cpuUsage': 'CPU usage %'; + readonly 'podsTable.columns.memoryUsage': 'Memory usage %'; + readonly 'podsTable.unknown': 'unknown'; + readonly 'podsTable.status.running': 'Running'; + readonly 'podsTable.status.ok': 'OK'; + readonly 'fixDialog.title': '{{podName}} - {{errorType}}'; + readonly 'fixDialog.events': 'Events:'; + readonly 'fixDialog.helpButton': 'Help'; + readonly 'fixDialog.detectedError': 'Detected error:'; + readonly 'fixDialog.causeExplanation': 'Cause explanation:'; + readonly 'fixDialog.fix': 'Fix:'; + readonly 'fixDialog.crashLogs': 'Crash logs:'; + readonly 'fixDialog.openDocs': 'Open docs'; + readonly 'fixDialog.ariaLabels.close': 'close'; + readonly 'fixDialog.ariaLabels.fixIssue': 'fix issue'; readonly 'podDrawer.buttons.delete': 'Delete Pod'; + readonly 'podDrawer.cpuRequests': 'CPU requests'; + readonly 'podDrawer.cpuLimits': 'CPU limits'; + readonly 'podDrawer.memoryRequests': 'Memory requests'; + readonly 'podDrawer.memoryLimits': 'Memory limits'; + readonly 'podDrawer.resourceUtilization': 'Resource utilization'; + readonly 'hpa.minReplicas': 'min replicas'; + readonly 'hpa.maxReplicas': 'max replicas'; + readonly 'hpa.replicasSummary': 'min replicas {{min}} / max replicas {{max}}'; + readonly 'hpa.currentCpuUsage': 'current CPU usage:'; + readonly 'hpa.currentCpuUsageLabel': 'current CPU usage: {{value}}%'; + readonly 'hpa.targetCpuUsage': 'target CPU usage:'; + readonly 'hpa.targetCpuUsageLabel': 'target CPU usage: {{value}}%'; + readonly 'errorReporting.columns.name': 'name'; + readonly 'errorReporting.columns.kind': 'kind'; + readonly 'errorReporting.columns.namespace': 'namespace'; + readonly 'errorReporting.columns.messages': 'messages'; + readonly 'errorReporting.columns.cluster': 'cluster'; + readonly 'errorReporting.title': 'Error Reporting'; + readonly 'podLogs.title': 'No logs emitted'; + readonly 'podLogs.description': 'No logs were emitted by the container'; + readonly 'podLogs.buttonText': 'Logs'; + readonly 'podLogs.titleTemplate': '{{podName}} - {{containerName}} logs on cluster {{clusterName}}'; + readonly 'podLogs.buttonAriaLabel': 'get logs'; + readonly 'podExecTerminal.buttonText': 'Terminal'; + readonly 'podExecTerminal.titleTemplate': '{{podName}} - {{containerName}} terminal shell on cluster {{clusterName}}'; + readonly 'podExecTerminal.buttonAriaLabel': 'open terminal'; + readonly 'kubernetesDrawer.yaml': 'YAML'; + readonly 'kubernetesDrawer.closeDrawer': 'Close the drawer'; + readonly 'kubernetesDrawer.managedFields': 'Managed Fields'; + readonly 'kubernetesDrawer.unknownName': 'unknown name'; + readonly 'linkErrorPanel.message': "Could not format the link to the dashboard of your cluster named '{{clusterName}}'. Its dashboardApp property has been set to '{{dashboardApp}}.'"; + readonly 'linkErrorPanel.title': 'There was a problem formatting the link to the Kubernetes dashboard'; + readonly 'linkErrorPanel.errorsLabel': 'Errors:'; + readonly 'kubernetesDialog.closeAriaLabel': 'close'; } >; diff --git a/plugins/kubernetes-react/report.api.md b/plugins/kubernetes-react/report.api.md index 4bf48ecd92..497c8673c2 100644 --- a/plugins/kubernetes-react/report.api.md +++ b/plugins/kubernetes-react/report.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 { ApiRef } from '@backstage/core-plugin-api'; +import { ApiRef } from '@backstage/frontend-plugin-api'; import { AsyncState } from 'react-use/esm/useAsyncFn'; import { ClientContainerStatus } from '@backstage/plugin-kubernetes-common'; import { ClientPodStatus } from '@backstage/plugin-kubernetes-common'; diff --git a/plugins/kubernetes-react/src/__fixtures__/1-secrets.json b/plugins/kubernetes-react/src/__fixtures__/1-secrets.json new file mode 100644 index 0000000000..c11f7ff816 --- /dev/null +++ b/plugins/kubernetes-react/src/__fixtures__/1-secrets.json @@ -0,0 +1,26 @@ +{ + "secrets": [ + { + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "app-secret", + "namespace": "default", + "uid": "1ea073bc-7a4b-4b99-8321-0305bce85568", + "resourceVersion": "1362732552", + "creationTimestamp": "2021-07-16T22:39:58Z", + "labels": { + "backstage.io/kubernetes-id": "dice-roller", + "app": "dice-roller" + }, + "annotations": {} + }, + "data": { + "database.password": "***", + "api.key": "***", + "jwt.secret": "***", + "ssl.cert": "***" + } + } + ] +} diff --git a/plugins/kubernetes-react/src/__fixtures__/2-secrets.json b/plugins/kubernetes-react/src/__fixtures__/2-secrets.json new file mode 100644 index 0000000000..bdc881230a --- /dev/null +++ b/plugins/kubernetes-react/src/__fixtures__/2-secrets.json @@ -0,0 +1,47 @@ +{ + "secrets": [ + { + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "app-secret", + "namespace": "default", + "uid": "1ea073bc-7a4b-4b99-8321-0305bce85568", + "resourceVersion": "1362732552", + "creationTimestamp": "2021-07-16T22:39:58Z", + "labels": { + "backstage.io/kubernetes-id": "dice-roller", + "app": "dice-roller" + }, + "annotations": {} + }, + "data": { + "database.password": "***", + "api.key": "***", + "jwt.secret": "***", + "ssl.cert": "***" + } + }, + { + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "redis-secret", + "namespace": "default", + "uid": "2ea073bc-7a4b-4b99-8321-0305bce85568", + "resourceVersion": "1362732553", + "creationTimestamp": "2021-07-16T22:40:58Z", + "labels": { + "backstage.io/kubernetes-id": "dice-roller", + "app": "redis" + }, + "annotations": {} + }, + "data": { + "redis.password": "***", + "redis.auth": "***", + "admin.token": "***" + } + } + ] +} diff --git a/plugins/kubernetes-react/src/api/KubernetesBackendClient.test.ts b/plugins/kubernetes-react/src/api/KubernetesBackendClient.test.ts index 4698f01dfe..3e8b7aa437 100644 --- a/plugins/kubernetes-react/src/api/KubernetesBackendClient.test.ts +++ b/plugins/kubernetes-react/src/api/KubernetesBackendClient.test.ts @@ -16,7 +16,7 @@ import { KubernetesAuthProvidersApi } from '../kubernetes-auth-provider'; import { KubernetesBackendClient } from './KubernetesBackendClient'; -import { rest } from 'msw'; +import { http, HttpResponse } from 'msw'; import { UrlPatternDiscovery } from '@backstage/core-app-api'; import { setupServer } from 'msw/node'; import { MockFetchApi, registerMswTestHooks } from '@backstage/test-utils'; @@ -89,23 +89,24 @@ describe('KubernetesBackendClient', () => { it('hits the /clusters API', async () => { identityApi.getCredentials.mockResolvedValue({ token: 'idToken' }); worker.use( - rest.get('http://localhost:1234/api/kubernetes/clusters', (_, res, ctx) => - res(ctx.json({ items: [{ name: 'cluster-a', authProvider: 'aws' }] })), + http.get('http://localhost:1234/api/kubernetes/clusters', () => + HttpResponse.json({ + items: [{ name: 'cluster-a', authProvider: 'aws' }], + }), ), ); const clusters = await backendClient.getClusters(); - expect(clusters).toStrictEqual([ - { name: 'cluster-a', authProvider: 'aws' }, - ]); + expect(clusters).toEqual([{ name: 'cluster-a', authProvider: 'aws' }]); }); it('/clusters API throws a 404 Error', async () => { identityApi.getCredentials.mockResolvedValue({ token: 'idToken' }); worker.use( - rest.get('http://localhost:1234/api/kubernetes/clusters', (_, res, ctx) => - res(ctx.status(404)), + http.get( + 'http://localhost:1234/api/kubernetes/clusters', + () => new HttpResponse(null, { status: 404 }), ), ); @@ -117,8 +118,9 @@ describe('KubernetesBackendClient', () => { it('/clusters API throws a 500 Error', async () => { identityApi.getCredentials.mockResolvedValue({ token: 'idToken' }); worker.use( - rest.get('http://localhost:1234/api/kubernetes/clusters', (_, res, ctx) => - res(ctx.status(500)), + http.get( + 'http://localhost:1234/api/kubernetes/clusters', + () => new HttpResponse(null, { status: 500 }), ), ); @@ -130,9 +132,9 @@ describe('KubernetesBackendClient', () => { it('hits the /resources/custom/query API', async () => { identityApi.getCredentials.mockResolvedValue({ token: 'idToken' }); worker.use( - rest.post( + http.post( 'http://localhost:1234/api/kubernetes/resources/custom/query', - (_, res, ctx) => res(ctx.json(mockResponse)), + () => HttpResponse.json(mockResponse), ), ); @@ -157,15 +159,15 @@ describe('KubernetesBackendClient', () => { const customObject: ObjectsByEntityResponse = await backendClient.getCustomObjectsByEntity(request); - expect(customObject).toStrictEqual(mockResponse); + expect(customObject).toEqual(mockResponse); }); it('/resources/custom/query API throws a 404 error', async () => { identityApi.getCredentials.mockResolvedValue({ token: 'idToken' }); worker.use( - rest.post( + http.post( 'http://localhost:1234/api/kubernetes/resources/custom/query', - (_, res, ctx) => res(ctx.status(404)), + () => new HttpResponse(null, { status: 404 }), ), ); @@ -197,9 +199,9 @@ describe('KubernetesBackendClient', () => { it('/resources/custom/query API throws a 500 error', async () => { identityApi.getCredentials.mockResolvedValue({ token: 'idToken' }); worker.use( - rest.post( + http.post( 'http://localhost:1234/api/kubernetes/resources/custom/query', - (_, res, ctx) => res(ctx.status(500)), + () => new HttpResponse(null, { status: 500 }), ), ); @@ -231,9 +233,8 @@ describe('KubernetesBackendClient', () => { it('hits the /services/{entityName} API', async () => { identityApi.getCredentials.mockResolvedValue({ token: 'idToken' }); worker.use( - rest.post( - 'http://localhost:1234/api/kubernetes/services/test-name', - (_, res, ctx) => res(ctx.json(mockResponse)), + http.post('http://localhost:1234/api/kubernetes/services/test-name', () => + HttpResponse.json(mockResponse), ), ); @@ -250,15 +251,15 @@ describe('KubernetesBackendClient', () => { const entityObject: ObjectsByEntityResponse = await backendClient.getObjectsByEntity(request); - expect(entityObject).toStrictEqual(mockResponse); + expect(entityObject).toEqual(mockResponse); }); it('services/{entityName} API throws a 404 error', async () => { identityApi.getCredentials.mockResolvedValue({ token: 'idToken' }); worker.use( - rest.post( + http.post( 'http://localhost:1234/api/kubernetes/services/test-name', - (_, res, ctx) => res(ctx.status(404)), + () => new HttpResponse(null, { status: 404 }), ), ); @@ -282,9 +283,9 @@ describe('KubernetesBackendClient', () => { it('services/{entityName} API throws a 500 error', async () => { identityApi.getCredentials.mockResolvedValue({ token: 'idToken' }); worker.use( - rest.post( + http.post( 'http://localhost:1234/api/kubernetes/services/test-name', - (_, res, ctx) => res(ctx.status(500)), + () => new HttpResponse(null, { status: 500 }), ), ); @@ -308,9 +309,9 @@ describe('KubernetesBackendClient', () => { it('hits the /resources/workloads/query API', async () => { identityApi.getCredentials.mockResolvedValue({ token: 'idToken' }); worker.use( - rest.post( + http.post( 'http://localhost:1234/api/kubernetes/resources/workloads/query', - (_, res, ctx) => res(ctx.json(mockResponse)), + () => HttpResponse.json(mockResponse), ), ); @@ -328,15 +329,15 @@ describe('KubernetesBackendClient', () => { const response: ObjectsByEntityResponse = await backendClient.getWorkloadsByEntity(request); - expect(response).toStrictEqual(mockResponse); + expect(response).toEqual(mockResponse); }); it('/resources/workloads/query API throws a 404 error', async () => { identityApi.getCredentials.mockResolvedValue({ token: 'idToken' }); worker.use( - rest.post( + http.post( 'http://localhost:1234/api/kubernetes/resources/workloads/query', - (_, res, ctx) => res(ctx.status(404)), + () => new HttpResponse(null, { status: 404 }), ), ); @@ -361,9 +362,9 @@ describe('KubernetesBackendClient', () => { it('/resources/workloads/query API throws a 500 error', async () => { identityApi.getCredentials.mockResolvedValue({ token: 'idToken' }); worker.use( - rest.post( + http.post( 'http://localhost:1234/api/kubernetes/resources/workloads/query', - (_, res, ctx) => res(ctx.status(500)), + () => new HttpResponse(null, { status: 500 }), ), ); @@ -388,12 +389,10 @@ describe('KubernetesBackendClient', () => { describe('proxy', () => { beforeEach(() => { worker.use( - rest.get( - 'http://localhost:1234/api/kubernetes/clusters', - (_, res, ctx) => - res( - ctx.json({ items: [{ name: 'cluster-a', authProvider: 'aws' }] }), - ), + http.get('http://localhost:1234/api/kubernetes/clusters', () => + HttpResponse.json({ + items: [{ name: 'cluster-a', authProvider: 'aws' }], + }), ), ); identityApi.getCredentials.mockResolvedValue({ token: 'idToken' }); @@ -401,20 +400,16 @@ describe('KubernetesBackendClient', () => { it('hits the /proxy API with oidc as protocol and okta as auth provider', async () => { worker.use( - rest.get( - 'http://localhost:1234/api/kubernetes/clusters', - (_, res, ctx) => - res( - ctx.json({ - items: [ - { - name: 'cluster-a', - authProvider: 'oidc', - oidcTokenProvider: 'okta', - }, - ], - }), - ), + http.get('http://localhost:1234/api/kubernetes/clusters', () => + HttpResponse.json({ + items: [ + { + name: 'cluster-a', + authProvider: 'oidc', + oidcTokenProvider: 'okta', + }, + ], + }), ), ); kubernetesAuthProvidersApi.getCredentials.mockResolvedValue({ @@ -428,16 +423,14 @@ describe('KubernetesBackendClient', () => { }, }; worker.use( - rest.get( + http.get( 'http://localhost:1234/api/kubernetes/proxy/api/v1/namespaces', - (req, res, ctx) => - res( - req.headers.get( - 'Backstage-Kubernetes-Authorization-oidc-okta', - ) === 'k8-token3' - ? ctx.json(nsResponse) - : ctx.status(403), - ), + ({ request }) => + request.headers.get( + 'Backstage-Kubernetes-Authorization-oidc-okta', + ) === 'k8-token3' + ? HttpResponse.json(nsResponse) + : new HttpResponse(null, { status: 403 }), ), ); @@ -448,7 +441,7 @@ describe('KubernetesBackendClient', () => { const response = await backendClient.proxy(request); - await expect(response.json()).resolves.toStrictEqual(nsResponse); + await expect(response.json()).resolves.toEqual(nsResponse); expect(kubernetesAuthProvidersApi.getCredentials).toHaveBeenCalledWith( 'oidc.okta', ); @@ -457,19 +450,15 @@ describe('KubernetesBackendClient', () => { it('hits the /proxy API with serviceAccount as auth provider', async () => { identityApi.getCredentials.mockResolvedValue({ token: 'idToken' }); worker.use( - rest.get( - 'http://localhost:1234/api/kubernetes/clusters', - (_, res, ctx) => - res( - ctx.json({ - items: [ - { - name: 'cluster-a', - authProvider: 'serviceAccount', - }, - ], - }), - ), + http.get('http://localhost:1234/api/kubernetes/clusters', () => + HttpResponse.json({ + items: [ + { + name: 'cluster-a', + authProvider: 'serviceAccount', + }, + ], + }), ), ); @@ -481,14 +470,12 @@ describe('KubernetesBackendClient', () => { }, }; worker.use( - rest.get( + http.get( 'http://localhost:1234/api/kubernetes/proxy/api/v1/namespaces', - (req, res, ctx) => - res( - req.headers.get('Authorization') === 'Bearer idToken' - ? ctx.json(nsResponse) - : ctx.status(403), - ), + ({ request }) => + request.headers.get('Authorization') === 'Bearer idToken' + ? HttpResponse.json(nsResponse) + : new HttpResponse(null, { status: 403 }), ), ); @@ -499,7 +486,7 @@ describe('KubernetesBackendClient', () => { const response = await backendClient.proxy(request); - await expect(response.json()).resolves.toStrictEqual(nsResponse); + await expect(response.json()).resolves.toEqual(nsResponse); expect(kubernetesAuthProvidersApi.getCredentials).toHaveBeenCalledWith( 'serviceAccount', ); @@ -507,24 +494,20 @@ describe('KubernetesBackendClient', () => { it('ignores oidcTokenProvider for non-oidc auth provider', async () => { worker.use( - rest.get( - 'http://localhost:1234/api/kubernetes/clusters', - (_, res, ctx) => - res( - ctx.json({ - items: [ - { - name: 'cluster-a', - authProvider: 'not oidc', - oidcTokenProvider: 'should be ignored', - }, - ], - }), - ), + http.get('http://localhost:1234/api/kubernetes/clusters', () => + HttpResponse.json({ + items: [ + { + name: 'cluster-a', + authProvider: 'not oidc', + oidcTokenProvider: 'should be ignored', + }, + ], + }), ), - rest.get( + http.get( 'http://localhost:1234/api/kubernetes/proxy/api/v1/namespaces', - (_, res, ctx) => res(ctx.json([])), + () => HttpResponse.json([]), ), ); @@ -552,15 +535,13 @@ describe('KubernetesBackendClient', () => { }, }; worker.use( - rest.get( + http.get( 'http://localhost:1234/api/kubernetes/proxy/api/v1/namespaces', - (req, res, ctx) => - res( - req.headers.get('Backstage-Kubernetes-Authorization-aws') === - 'k8-token' - ? ctx.json(nsResponse) - : ctx.status(403), - ), + ({ request }) => + request.headers.get('Backstage-Kubernetes-Authorization-aws') === + 'k8-token' + ? HttpResponse.json(nsResponse) + : new HttpResponse(null, { status: 403 }), ), ); @@ -570,7 +551,7 @@ describe('KubernetesBackendClient', () => { }; const response = await backendClient.proxy(request); - await expect(response.json()).resolves.toStrictEqual(nsResponse); + await expect(response.json()).resolves.toEqual(nsResponse); }); it('/proxy API throws a 404 error', async () => { @@ -578,9 +559,9 @@ describe('KubernetesBackendClient', () => { token: 'k8-token', }); worker.use( - rest.get( + http.get( 'http://localhost:1234/api/kubernetes/proxy/api/v1/namespaces', - (_, res, ctx) => res(ctx.status(404)), + () => new HttpResponse(null, { status: 404 }), ), ); @@ -616,15 +597,13 @@ describe('KubernetesBackendClient', () => { }, }; worker.use( - rest.get( + http.get( 'http://localhost:1234/api/kubernetes/proxy/api/v1/namespaces', - (req, res, ctx) => - res( - req.headers.get('Backstage-Kubernetes-Authorization') === - 'Bearer k8-token' - ? ctx.json(nsResponse) - : ctx.status(403), - ), + ({ request }) => + request.headers.get('Backstage-Kubernetes-Authorization') === + 'Bearer k8-token' + ? HttpResponse.json(nsResponse) + : new HttpResponse(null, { status: 403 }), ), ); @@ -646,14 +625,12 @@ describe('KubernetesBackendClient', () => { }, }; worker.use( - rest.get( + http.get( 'http://localhost:1234/api/kubernetes/proxy/api/v1/namespaces/new-ns', - (req, res, ctx) => - res( - req.headers.get('Backstage-Kubernetes-Authorization') - ? ctx.status(403) - : ctx.json(nsResponse), - ), + ({ request }) => + request.headers.get('Backstage-Kubernetes-Authorization') + ? new HttpResponse(null, { status: 403 }) + : HttpResponse.json(nsResponse), ), ); kubernetesAuthProvidersApi.getCredentials.mockResolvedValue({}); @@ -663,7 +640,7 @@ describe('KubernetesBackendClient', () => { path: '/api/v1/namespaces/new-ns', }); - await expect(response.json()).resolves.toStrictEqual(nsResponse); + await expect(response.json()).resolves.toEqual(nsResponse); }); }); }); diff --git a/plugins/kubernetes-react/src/api/formatters/HeadlampClusterLinksFormatter.test.ts b/plugins/kubernetes-react/src/api/formatters/HeadlampClusterLinksFormatter.test.ts index 533e966137..12de678718 100644 --- a/plugins/kubernetes-react/src/api/formatters/HeadlampClusterLinksFormatter.test.ts +++ b/plugins/kubernetes-react/src/api/formatters/HeadlampClusterLinksFormatter.test.ts @@ -21,11 +21,6 @@ describe('HeadlampClusterLinksFormatter', () => { beforeEach(() => { formatter = new HeadlampClusterLinksFormatter(); - // Mock window.location.origin - Object.defineProperty(window, 'location', { - value: { origin: 'http://localhost:3000' }, - writable: true, - }); }); it('formats internal dashboard link correctly', async () => { @@ -41,7 +36,7 @@ describe('HeadlampClusterLinksFormatter', () => { const result = await formatter.formatClusterLink(options); expect(result.toString()).toBe( - 'http://localhost:3000/headlamp?to=%2Fc%2Ftest-cluster%2Fpods%2Fdefault%2Ftest-pod', + 'http://localhost/headlamp?to=%2Fc%2Ftest-cluster%2Fpods%2Fdefault%2Ftest-pod', ); }); diff --git a/plugins/kubernetes-react/src/components/Cluster/Cluster.tsx b/plugins/kubernetes-react/src/components/Cluster/Cluster.tsx index 4e8c4a1947..ca0f57e855 100644 --- a/plugins/kubernetes-react/src/components/Cluster/Cluster.tsx +++ b/plugins/kubernetes-react/src/components/Cluster/Cluster.tsx @@ -42,6 +42,9 @@ import { import { StatusError, StatusOK } from '@backstage/core-components'; import { PodMetricsContext } from '../../hooks/usePodMetrics'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../translation'; +import { SecretsAccordions } from '../SecretsAccordions'; type ClusterSummaryProps = { clusterName: string; @@ -55,6 +58,7 @@ const ClusterSummary = ({ totalNumberOfPods, numberOfPodsWithErrors, }: ClusterSummaryProps) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); return ( {clusterName} - Cluster + {t('cluster.label')} @@ -89,13 +93,15 @@ const ClusterSummary = ({ spacing={0} > - {totalNumberOfPods} pods + {t('cluster.pods', { count: totalNumberOfPods })} {numberOfPodsWithErrors > 0 ? ( - {numberOfPodsWithErrors} pods with errors + + {t('cluster.podsWithErrors', { count: numberOfPodsWithErrors })} + ) : ( - No pods with errors + {t('cluster.noPodsWithErrors')} )} @@ -176,6 +182,11 @@ export const Cluster = ({ clusterObjects, podsWithErrors }: ClusterProps) => { ) : undefined} + {groupedResponses.secrets.length > 0 ? ( + + + + ) : undefined} {groupedResponses.cronJobs.length > 0 ? ( diff --git a/plugins/kubernetes-react/src/components/ConfigmapsAccordions/ConfigmapsDrawer.tsx b/plugins/kubernetes-react/src/components/ConfigmapsAccordions/ConfigmapsDrawer.tsx index 97e8aab723..729355de5f 100644 --- a/plugins/kubernetes-react/src/components/ConfigmapsAccordions/ConfigmapsDrawer.tsx +++ b/plugins/kubernetes-react/src/components/ConfigmapsAccordions/ConfigmapsDrawer.tsx @@ -18,6 +18,8 @@ import Typography from '@material-ui/core/Typography'; import Grid from '@material-ui/core/Grid'; import Chip from '@material-ui/core/Chip'; import type { V1ConfigMap } from '@kubernetes/client-node'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../translation'; export const ConfigmapsDrawer = ({ configmap, @@ -26,6 +28,7 @@ export const ConfigmapsDrawer = ({ configmap: V1ConfigMap; expanded?: boolean; }) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); const namespace = configmap.metadata?.namespace; return ( {namespace && ( - + )} diff --git a/plugins/kubernetes-react/src/components/CronJobsAccordions/CronJobsDrawer.tsx b/plugins/kubernetes-react/src/components/CronJobsAccordions/CronJobsDrawer.tsx index a7ffdca854..8d21f73ce3 100644 --- a/plugins/kubernetes-react/src/components/CronJobsAccordions/CronJobsDrawer.tsx +++ b/plugins/kubernetes-react/src/components/CronJobsAccordions/CronJobsDrawer.tsx @@ -18,6 +18,8 @@ import { KubernetesStructuredMetadataTableDrawer } from '../KubernetesDrawer'; import Typography from '@material-ui/core/Typography'; import Grid from '@material-ui/core/Grid'; import Chip from '@material-ui/core/Chip'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../translation'; export const CronJobDrawer = ({ cronJob, @@ -26,6 +28,7 @@ export const CronJobDrawer = ({ cronJob: V1CronJob; expanded?: boolean; }) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); const namespace = cronJob.metadata?.namespace; return ( {namespace && ( - + )} diff --git a/plugins/kubernetes-react/src/components/CustomResources/ArgoRollouts/Rollout.tsx b/plugins/kubernetes-react/src/components/CustomResources/ArgoRollouts/Rollout.tsx index a299d412c9..93334c2b35 100644 --- a/plugins/kubernetes-react/src/components/CustomResources/ArgoRollouts/Rollout.tsx +++ b/plugins/kubernetes-react/src/components/CustomResources/ArgoRollouts/Rollout.tsx @@ -39,6 +39,8 @@ import { } from '../../../utils/owner'; import { StatusError, StatusOK } from '@backstage/core-components'; import { READY_COLUMNS, RESOURCE_COLUMNS } from '../../Pods/PodsTable'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../../translation'; type RolloutAccordionsProps = { rollouts: any[]; @@ -89,6 +91,7 @@ const RolloutSummary = ({ numberOfPodsWithErrors, hpa, }: RolloutSummaryProps) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); const pauseTime: string | undefined = rollout.status?.pauseConditions?.find( (p: any) => p.reason === 'CanaryPauseStep', )?.startTime; @@ -125,18 +128,24 @@ const RolloutSummary = ({ > - min replicas {hpa.spec?.minReplicas ?? '?'} / max replicas{' '} - {hpa.spec?.maxReplicas ?? '?'} + {t('hpa.replicasSummary', { + min: String(hpa.spec?.minReplicas ?? '?'), + max: String(hpa.spec?.maxReplicas ?? '?'), + })} - current CPU usage: {cpuUtil ?? '?'}% + {t('hpa.currentCpuUsageLabel', { + value: String(cpuUtil ?? '?'), + })} - target CPU usage: {specCpuUtil ?? '?'}% + {t('hpa.targetCpuUsageLabel', { + value: String(specCpuUtil ?? '?'), + })} @@ -153,16 +162,15 @@ const RolloutSummary = ({ spacing={0} > - {numberOfCurrentPods} pods + {t('pods.pods', { count: numberOfCurrentPods })} {numberOfPodsWithErrors > 0 ? ( - {numberOfPodsWithErrors} pod - {numberOfPodsWithErrors > 1 ? 's' : ''} with errors + {t('cluster.podsWithErrors', { count: numberOfPodsWithErrors })} ) : ( - No pods with errors + {t('cluster.noPodsWithErrors')} )} diff --git a/plugins/kubernetes-react/src/components/CustomResources/ArgoRollouts/RolloutDrawer.tsx b/plugins/kubernetes-react/src/components/CustomResources/ArgoRollouts/RolloutDrawer.tsx index 5a42e0971c..d02d9bd9fe 100644 --- a/plugins/kubernetes-react/src/components/CustomResources/ArgoRollouts/RolloutDrawer.tsx +++ b/plugins/kubernetes-react/src/components/CustomResources/ArgoRollouts/RolloutDrawer.tsx @@ -18,6 +18,8 @@ import { KubernetesStructuredMetadataTableDrawer } from '../../KubernetesDrawer' import Typography from '@material-ui/core/Typography'; import Grid from '@material-ui/core/Grid'; import Chip from '@material-ui/core/Chip'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../../translation'; export const RolloutDrawer = ({ rollout, @@ -26,6 +28,7 @@ export const RolloutDrawer = ({ rollout: any; expanded?: boolean; }) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); const namespace = rollout.metadata?.namespace; return ( {namespace && ( - + )} diff --git a/plugins/kubernetes-react/src/components/DaemonSetsAccordions/DaemonSetsAccordions.tsx b/plugins/kubernetes-react/src/components/DaemonSetsAccordions/DaemonSetsAccordions.tsx index a96034a012..b2f5645444 100644 --- a/plugins/kubernetes-react/src/components/DaemonSetsAccordions/DaemonSetsAccordions.tsx +++ b/plugins/kubernetes-react/src/components/DaemonSetsAccordions/DaemonSetsAccordions.tsx @@ -30,6 +30,8 @@ import { } from '../../hooks'; import { StatusError, StatusOK } from '@backstage/core-components'; import { READY_COLUMNS, RESOURCE_COLUMNS } from '../Pods/PodsTable'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../translation'; type DaemonSetsAccordionsProps = { children?: ReactNode; @@ -53,6 +55,7 @@ const DaemonSetSummary = ({ numberOfCurrentPods, numberOfPodsWithErrors, }: DaemonSetSummaryProps) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); return ( - {numberOfCurrentPods} pods + {t('pods.pods', { count: numberOfCurrentPods })} {numberOfPodsWithErrors > 0 ? ( - {numberOfPodsWithErrors} pod - {numberOfPodsWithErrors > 1 ? 's' : ''} with errors + {t('cluster.podsWithErrors', { count: numberOfPodsWithErrors })} ) : ( - No pods with errors + {t('cluster.noPodsWithErrors')} )} diff --git a/plugins/kubernetes-react/src/components/DaemonSetsAccordions/DaemonSetsDrawer.tsx b/plugins/kubernetes-react/src/components/DaemonSetsAccordions/DaemonSetsDrawer.tsx index 400512f0d2..00f41cdf69 100644 --- a/plugins/kubernetes-react/src/components/DaemonSetsAccordions/DaemonSetsDrawer.tsx +++ b/plugins/kubernetes-react/src/components/DaemonSetsAccordions/DaemonSetsDrawer.tsx @@ -19,6 +19,8 @@ import { KubernetesStructuredMetadataTableDrawer } from '../KubernetesDrawer'; import Typography from '@material-ui/core/Typography'; import Grid from '@material-ui/core/Grid'; import Chip from '@material-ui/core/Chip'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../translation'; export const DaemonSetDrawer = ({ daemonset, @@ -27,6 +29,7 @@ export const DaemonSetDrawer = ({ daemonset: V1DaemonSet; expanded?: boolean; }) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); const namespace = daemonset.metadata?.namespace; return ( {namespace && ( - + )} diff --git a/plugins/kubernetes-react/src/components/DeploymentsAccordions/DeploymentDrawer.tsx b/plugins/kubernetes-react/src/components/DeploymentsAccordions/DeploymentDrawer.tsx index c94059ccc2..31bee930d3 100644 --- a/plugins/kubernetes-react/src/components/DeploymentsAccordions/DeploymentDrawer.tsx +++ b/plugins/kubernetes-react/src/components/DeploymentsAccordions/DeploymentDrawer.tsx @@ -21,6 +21,8 @@ import { renderCondition } from '../../utils/pod'; import Typography from '@material-ui/core/Typography'; import Grid from '@material-ui/core/Grid'; import Chip from '@material-ui/core/Chip'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../translation'; export const DeploymentDrawer = ({ deployment, @@ -29,6 +31,7 @@ export const DeploymentDrawer = ({ deployment: V1Deployment; expanded?: boolean; }) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); const namespace = deployment.metadata?.namespace; return ( {namespace && ( - + )} diff --git a/plugins/kubernetes-react/src/components/DeploymentsAccordions/DeploymentsAccordions.tsx b/plugins/kubernetes-react/src/components/DeploymentsAccordions/DeploymentsAccordions.tsx index 75da5e7815..961d07b761 100644 --- a/plugins/kubernetes-react/src/components/DeploymentsAccordions/DeploymentsAccordions.tsx +++ b/plugins/kubernetes-react/src/components/DeploymentsAccordions/DeploymentsAccordions.tsx @@ -39,6 +39,8 @@ import { } from '../../hooks'; import { StatusError, StatusOK } from '@backstage/core-components'; import { READY_COLUMNS, RESOURCE_COLUMNS } from '../Pods/PodsTable'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../translation'; type DeploymentsAccordionsProps = { children?: ReactNode; @@ -65,6 +67,7 @@ const DeploymentSummary = ({ numberOfPodsWithErrors, hpa, }: DeploymentSummaryProps) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); const specCpuUtil = hpa?.spec?.metrics?.find( metric => metric.type === 'Resource' && metric.resource?.name === 'cpu', )?.resource?.target.averageUtilization; @@ -97,18 +100,24 @@ const DeploymentSummary = ({ > - min replicas {hpa.spec?.minReplicas ?? '?'} / max replicas{' '} - {hpa.spec?.maxReplicas ?? '?'} + {t('hpa.replicasSummary', { + min: String(hpa.spec?.minReplicas ?? '?'), + max: String(hpa.spec?.maxReplicas ?? '?'), + })} - current CPU usage: {cpuUtil ?? '?'}% + {t('hpa.currentCpuUsageLabel', { + value: String(cpuUtil ?? '?'), + })} - target CPU usage: {specCpuUtil ?? '?'}% + {t('hpa.targetCpuUsageLabel', { + value: String(specCpuUtil ?? '?'), + })} @@ -125,16 +134,15 @@ const DeploymentSummary = ({ spacing={0} > - {numberOfCurrentPods} pods + {t('pods.pods', { count: numberOfCurrentPods })} {numberOfPodsWithErrors > 0 ? ( - {numberOfPodsWithErrors} pod - {numberOfPodsWithErrors > 1 ? 's' : ''} with errors + {t('cluster.podsWithErrors', { count: numberOfPodsWithErrors })} ) : ( - No pods with errors + {t('cluster.noPodsWithErrors')} )} diff --git a/plugins/kubernetes-react/src/components/ErrorPanel/ErrorPanel.tsx b/plugins/kubernetes-react/src/components/ErrorPanel/ErrorPanel.tsx index 5238a76e70..b036c2b089 100644 --- a/plugins/kubernetes-react/src/components/ErrorPanel/ErrorPanel.tsx +++ b/plugins/kubernetes-react/src/components/ErrorPanel/ErrorPanel.tsx @@ -18,22 +18,37 @@ import { ReactNode } from 'react'; import Typography from '@material-ui/core/Typography'; import { ClusterObjects } from '@backstage/plugin-kubernetes-common'; import { WarningPanel } from '@backstage/core-components'; +import { + useTranslationRef, + TranslationFunction, +} from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../translation'; const clustersWithErrorsToErrorMessage = ( clustersWithErrors: ClusterObjects[], + t: TranslationFunction, ): ReactNode => { return clustersWithErrors.map((c, i) => { return (
- {`Cluster: ${ - c.cluster.title || c.cluster.name - }`} + + {t('errorPanel.clusterLabelValue', { + cluster: c.cluster.title || c.cluster.name, + })} + {c.errors.map((e, j) => { return ( {e.errorType === 'FETCH_ERROR' - ? `Error communicating with Kubernetes: ${e.errorType}, message: ${e.message}` - : `Error fetching Kubernetes resource: '${e.resourcePath}', error: ${e.errorType}, status code: ${e.statusCode}`} + ? t('errorPanel.fetchError', { + errorType: e.errorType, + message: e.message, + }) + : t('errorPanel.resourceError', { + resourcePath: e.resourcePath ?? '', + errorType: e.errorType, + statusCode: String(e.statusCode ?? ''), + })} ); })} @@ -64,16 +79,24 @@ export const ErrorPanel = ({ entityName, errorMessage, clustersWithErrors, -}: ErrorPanelProps) => ( - - {clustersWithErrors && ( -
Errors: {clustersWithErrorsToErrorMessage(clustersWithErrors)}
- )} - {errorMessage && ( - Errors: {errorMessage} - )} -
-); +}: ErrorPanelProps) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); + return ( + + {clustersWithErrors && ( +
+ {t('errorPanel.errorsLabel')}:{' '} + {clustersWithErrorsToErrorMessage(clustersWithErrors, t)} +
+ )} + {errorMessage && ( + + {t('errorPanel.errorsLabel')}: {errorMessage} + + )} +
+ ); +}; diff --git a/plugins/kubernetes-react/src/components/ErrorReporting/ErrorReporting.tsx b/plugins/kubernetes-react/src/components/ErrorReporting/ErrorReporting.tsx index f9e86110c2..df23328b3f 100644 --- a/plugins/kubernetes-react/src/components/ErrorReporting/ErrorReporting.tsx +++ b/plugins/kubernetes-react/src/components/ErrorReporting/ErrorReporting.tsx @@ -13,12 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { useMemo } from 'react'; import { ClusterAttributes, DetectedError, DetectedErrorsByCluster, } from '@backstage/plugin-kubernetes-common'; import { Table, TableColumn } from '@backstage/core-components'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../translation'; /** * @@ -30,36 +33,6 @@ export type ErrorReportingProps = { clusters: ClusterAttributes[]; }; -const columns: TableColumn[] = [ - { - title: 'cluster', - width: '10%', - render: (row: Row) => row.cluster.title || row.cluster.name, - }, - { - title: 'namespace', - width: '10%', - render: (row: Row) => row.error.sourceRef.namespace, - }, - { - title: 'kind', - width: '10%', - render: (row: Row) => row.error.sourceRef.kind, - }, - { - title: 'name', - width: '30%', - render: (row: Row) => { - return <>{row.error.sourceRef.name} ; - }, - }, - { - title: 'messages', - width: '40%', - render: (row: Row) => row.error.message, - }, -]; - interface Row { cluster: ClusterAttributes; error: DetectedError; @@ -83,6 +56,7 @@ export const ErrorReporting = ({ detectedErrors, clusters, }: ErrorReportingProps) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); const errors = Array.from(detectedErrors.entries()) .flatMap(([clusterName, resourceErrors]) => { return resourceErrors.map(e => ({ @@ -92,11 +66,44 @@ export const ErrorReporting = ({ }) .sort(sortBySeverity); + const columns: TableColumn[] = useMemo( + () => [ + { + title: String(t('errorReporting.columns.cluster')), + width: '10%', + render: (row: Row) => row.cluster.title || row.cluster.name, + }, + { + title: String(t('errorReporting.columns.namespace')), + width: '10%', + render: (row: Row) => row.error.sourceRef.namespace, + }, + { + title: String(t('errorReporting.columns.kind')), + width: '10%', + render: (row: Row) => row.error.sourceRef.kind, + }, + { + title: String(t('errorReporting.columns.name')), + width: '30%', + render: (row: Row) => { + return <>{row.error.sourceRef.name} ; + }, + }, + { + title: String(t('errorReporting.columns.messages')), + width: '40%', + render: (row: Row) => row.error.message, + }, + ], + [t], + ); + return ( <> {errors.length !== 0 && (
{ + const { t } = useTranslationRef(kubernetesReactTranslationRef); const namespace = ingress.metadata?.namespace; return ( {namespace && ( - + )} diff --git a/plugins/kubernetes-react/src/components/KubernetesDialog/KubernetesDialog.tsx b/plugins/kubernetes-react/src/components/KubernetesDialog/KubernetesDialog.tsx index d1a2a8a825..182265d23e 100644 --- a/plugins/kubernetes-react/src/components/KubernetesDialog/KubernetesDialog.tsx +++ b/plugins/kubernetes-react/src/components/KubernetesDialog/KubernetesDialog.tsx @@ -22,6 +22,8 @@ import IconButton from '@material-ui/core/IconButton'; import { Theme, createStyles, makeStyles } from '@material-ui/core/styles'; import CloseIcon from '@material-ui/icons/Close'; import { ReactNode, useState } from 'react'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../translation'; const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -59,6 +61,7 @@ export const KubernetesDialog = ({ title, }: KubernetesDialogProps) => { const classes = useStyles(); + const { t } = useTranslationRef(kubernetesReactTranslationRef); const [open, setOpen] = useState(false); const openDialog = () => { @@ -80,7 +83,7 @@ export const KubernetesDialog = ({ {title} diff --git a/plugins/kubernetes-react/src/components/KubernetesDrawer/KubernetesDrawer.tsx b/plugins/kubernetes-react/src/components/KubernetesDrawer/KubernetesDrawer.tsx index 323ce650d9..aa0609ca06 100644 --- a/plugins/kubernetes-react/src/components/KubernetesDrawer/KubernetesDrawer.tsx +++ b/plugins/kubernetes-react/src/components/KubernetesDrawer/KubernetesDrawer.tsx @@ -31,6 +31,8 @@ import { } from '@material-ui/core/styles'; import CloseIcon from '@material-ui/icons/Close'; import { ManifestYaml } from './ManifestYaml'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../translation'; const useDrawerContentStyles = makeStyles((_theme: Theme) => createStyles({ @@ -72,6 +74,7 @@ const KubernetesDrawerContent = ({ close, }: KubernetesDrawerContentProps) => { const classes = useDrawerContentStyles(); + const { t } = useTranslationRef(kubernetesReactTranslationRef); const [isYaml, setIsYaml] = useState(false); return ( @@ -86,7 +89,7 @@ const KubernetesDrawerContent = ({ close()} color="inherit" > @@ -104,10 +107,10 @@ const KubernetesDrawerContent = ({ onChange={event => { setIsYaml(event.target.checked); }} - name="YAML" + name={t('kubernetesDrawer.yaml')} /> } - label="YAML" + label={t('kubernetesDrawer.yaml')} /> diff --git a/plugins/kubernetes-react/src/components/KubernetesDrawer/KubernetesStructuredMetadataTableDrawer.tsx b/plugins/kubernetes-react/src/components/KubernetesDrawer/KubernetesStructuredMetadataTableDrawer.tsx index fb52063177..fb9977fb4a 100644 --- a/plugins/kubernetes-react/src/components/KubernetesDrawer/KubernetesStructuredMetadataTableDrawer.tsx +++ b/plugins/kubernetes-react/src/components/KubernetesDrawer/KubernetesStructuredMetadataTableDrawer.tsx @@ -38,6 +38,8 @@ import { ManifestYaml } from './ManifestYaml'; import { useApi } from '@backstage/core-plugin-api'; import { kubernetesClusterLinkFormatterApiRef } from '../../api'; import useAsync from 'react-use/esm/useAsync'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../translation'; const useDrawerStyles = makeStyles((theme: Theme) => createStyles({ @@ -102,20 +104,24 @@ export type LinkErrorPanelProps = { export const LinkErrorPanel = ({ cluster, errorMessage, -}: LinkErrorPanelProps) => ( - - {errorMessage && ( - Errors: {errorMessage} - )} - -); +}: LinkErrorPanelProps) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); + return ( + + {errorMessage && ( + + {t('linkErrorPanel.errorsLabel')} {errorMessage} + + )} + + ); +}; /** * @@ -152,6 +158,7 @@ const KubernetesStructuredMetadataTableDrawerContent = < renderObject, kind, }: KubernetesStructuredMetadataTableDrawerContentProps) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); const [isYaml, setIsYaml] = useState(false); const formatter = useApi(kubernetesClusterLinkFormatterApiRef); @@ -175,13 +182,13 @@ const KubernetesStructuredMetadataTableDrawerContent = < - {object.metadata?.name ?? 'unknown name'} + {object.metadata?.name ?? t('kubernetesDrawer.unknownName')} toggleDrawer(e, false)} color="inherit" > @@ -201,10 +208,10 @@ const KubernetesStructuredMetadataTableDrawerContent = < onChange={event => { setIsYaml(event.target.checked); }} - name="YAML" + name={t('kubernetesDrawer.yaml')} /> } - label="YAML" + label={t('kubernetesDrawer.yaml')} /> diff --git a/plugins/kubernetes-react/src/components/KubernetesDrawer/ManifestYaml.tsx b/plugins/kubernetes-react/src/components/KubernetesDrawer/ManifestYaml.tsx index 3ccd32fcfb..604efc3055 100644 --- a/plugins/kubernetes-react/src/components/KubernetesDrawer/ManifestYaml.tsx +++ b/plugins/kubernetes-react/src/components/KubernetesDrawer/ManifestYaml.tsx @@ -18,6 +18,8 @@ import FormControlLabel from '@material-ui/core/FormControlLabel'; import Switch from '@material-ui/core/Switch'; import jsyaml from 'js-yaml'; import { useState } from 'react'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../translation'; /** * Props of ManifestYaml @@ -34,6 +36,7 @@ export interface ManifestYamlProps { * @public */ export const ManifestYaml = ({ object }: ManifestYamlProps) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); // Toggle whether the Kubernetes resource managed fields should be shown in // the YAML display. This toggle is only available when the YAML is being // shown because managed fields are never visible in the structured display. @@ -47,10 +50,10 @@ export const ManifestYaml = ({ object }: ManifestYamlProps) => { onChange={event => { setManagedFields(event.target.checked); }} - name="Managed Fields" + name={t('kubernetesDrawer.managedFields')} /> } - label="Managed Fields" + label={t('kubernetesDrawer.managedFields')} /> { const { cluster, containerName, podName } = props; - + const { t } = useTranslationRef(kubernetesReactTranslationRef); const isPodExecTerminalSupported = useIsPodExecTerminalSupported(); return ( !isPodExecTerminalSupported.loading && isPodExecTerminalSupported.value && ( } - buttonText="Terminal" + buttonText={t('podExecTerminal.buttonText')} disabled={ isPodExecTerminalSupported.loading || !isPodExecTerminalSupported.value } - title={`${podName} - ${containerName} terminal shell on cluster ${ - cluster.title || cluster.name - }`} + title={t('podExecTerminal.titleTemplate', { + podName, + containerName, + clusterName: cluster.title || cluster.name, + })} > diff --git a/plugins/kubernetes-react/src/components/Pods/ErrorList/ErrorList.test.tsx b/plugins/kubernetes-react/src/components/Pods/ErrorList/ErrorList.test.tsx index e460abea8b..ec7b24644a 100644 --- a/plugins/kubernetes-react/src/components/Pods/ErrorList/ErrorList.test.tsx +++ b/plugins/kubernetes-react/src/components/Pods/ErrorList/ErrorList.test.tsx @@ -17,44 +17,57 @@ import { render } from '@testing-library/react'; import { ErrorList } from './ErrorList'; import { Pod } from 'kubernetes-models/v1'; +import { TestApiProvider, mockApis } from '@backstage/test-utils'; +import { translationApiRef } from '@backstage/core-plugin-api/alpha'; +import { errorApiRef } from '@backstage/core-plugin-api'; describe('ErrorList', () => { + const translationApi = mockApis.translation(); + const errorApi = { post: jest.fn(), error$: jest.fn() }; + it('error highlight should render', () => { const { getByText } = render( - + , + ], + }, + ]} + /> + , ); expect(getByText('some-pod')).toBeInTheDocument(); expect(getByText('some error message')).toBeInTheDocument(); diff --git a/plugins/kubernetes-react/src/components/Pods/Events/Events.test.tsx b/plugins/kubernetes-react/src/components/Pods/Events/Events.test.tsx index 3f70bb700a..b74929a2e5 100644 --- a/plugins/kubernetes-react/src/components/Pods/Events/Events.test.tsx +++ b/plugins/kubernetes-react/src/components/Pods/Events/Events.test.tsx @@ -17,63 +17,25 @@ import { EventsContent } from './Events'; import { render } from '@testing-library/react'; import { Event } from 'kubernetes-models/v1'; import { DateTime } from 'luxon'; +import { TestApiProvider, mockApis } from '@backstage/test-utils'; +import { translationApiRef } from '@backstage/core-plugin-api/alpha'; +import { errorApiRef } from '@backstage/core-plugin-api'; describe('EventsContent', () => { const oneHourAgo = DateTime.now().minus({ hours: 1 }).toISO(); + const translationApi = mockApis.translation(); + const errorApi = { post: jest.fn(), error$: jest.fn() }; + it('should show info events', () => { const { getByText } = render( - , - ); - expect(getByText('First event 1 hour ago (count: 52)')).toBeInTheDocument(); - expect(getByText('something happened: hello there')).toBeInTheDocument(); - }); - it('should show warning events', () => { - const { getByText } = render( - , - ); - expect(getByText('First event 1 hour ago (count: 23)')).toBeInTheDocument(); - expect(getByText('something happened: uh oh')).toBeInTheDocument(); - }); - - it('should only show warning events when warningEventsOnly set', () => { - const { getByText, queryByText } = render( - + { metadata: { creationTimestamp: oneHourAgo, }, - }, - ] as Event[] - } - />, + } as Event, + ]} + /> + , + ); + expect(getByText('First event 1 hour ago (count: 52)')).toBeInTheDocument(); + expect(getByText('something happened: hello there')).toBeInTheDocument(); + }); + it('should show warning events', () => { + const { getByText } = render( + + + , + ); + expect(getByText('First event 1 hour ago (count: 23)')).toBeInTheDocument(); + expect(getByText('something happened: uh oh')).toBeInTheDocument(); + }); + + it('should only show warning events when warningEventsOnly set', () => { + const { getByText, queryByText } = render( + + + , ); expect(queryByText('First event 1 hour ago (count: 52)')).toBeNull(); expect(queryByText('something happened: hello there')).toBeNull(); diff --git a/plugins/kubernetes-react/src/components/Pods/Events/Events.tsx b/plugins/kubernetes-react/src/components/Pods/Events/Events.tsx index 94fc768e61..dc6abb6fd9 100644 --- a/plugins/kubernetes-react/src/components/Pods/Events/Events.tsx +++ b/plugins/kubernetes-react/src/components/Pods/Events/Events.tsx @@ -31,6 +31,8 @@ import { useEvents } from './useEvents'; import Skeleton from '@material-ui/lab/Skeleton'; import { DismissableBanner } from '@backstage/core-components'; import { Event } from 'kubernetes-models/v1'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../../translation'; /** * Props for Events @@ -59,8 +61,9 @@ export const EventsContent = ({ events, warningEventsOnly, }: EventsContentProps) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); if (events.length === 0) { - return No events found; + return {t('events.noEventsFound')}; } return ( diff --git a/plugins/kubernetes-react/src/components/Pods/FixDialog/FixDialog.tsx b/plugins/kubernetes-react/src/components/Pods/FixDialog/FixDialog.tsx index 644c17606b..65961dc22d 100644 --- a/plugins/kubernetes-react/src/components/Pods/FixDialog/FixDialog.tsx +++ b/plugins/kubernetes-react/src/components/Pods/FixDialog/FixDialog.tsx @@ -35,6 +35,8 @@ import { Events } from '../Events'; import { LinkButton } from '@backstage/core-components'; import List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../../translation'; const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -72,6 +74,7 @@ export const FixDialog: FC = ({ }: FixDialogProps) => { const [isOpen, setOpen] = useState(!!open); const classes = useStyles(); + const { t } = useTranslationRef(kubernetesReactTranslationRef); const openDialog = () => { setOpen(true); @@ -87,21 +90,27 @@ export const FixDialog: FC = ({ return ( - Detected error: + {t('fixDialog.detectedError')} {error.message} - Cause explanation: + + {t('fixDialog.causeExplanation')} + - {error.proposedFix?.rootCauseExplanation ?? 'unknown'} + {error.proposedFix?.rootCauseExplanation ?? t('podsTable.unknown')} - Fix: + {t('fixDialog.fix')} {(error.proposedFix?.actions ?? []).map((fix, i) => { return ( - + {fix} ); @@ -112,14 +121,15 @@ export const FixDialog: FC = ({ {pf && pf.type === 'logs' && ( <> - Crash logs: + {t('fixDialog.crashLogs')} = ({ {pf && pf.type === 'events' && ( <> - Events: + {t('fixDialog.events')} = ({ <> - {pod.metadata?.name} - {error.type} + {t('fixDialog.title', { + podName: pod.metadata?.name ?? '', + errorType: error.type, + })} @@ -178,7 +191,7 @@ export const FixDialog: FC = ({ target="_blank" rel="noopener" > - Open docs + {t('fixDialog.openDocs')} )} diff --git a/plugins/kubernetes-react/src/components/Pods/PodDrawer/ContainerCard.tsx b/plugins/kubernetes-react/src/components/Pods/PodDrawer/ContainerCard.tsx index 74ef538bf1..c2b750a7d9 100644 --- a/plugins/kubernetes-react/src/components/Pods/PodDrawer/ContainerCard.tsx +++ b/plugins/kubernetes-react/src/components/Pods/PodDrawer/ContainerCard.tsx @@ -30,6 +30,8 @@ import { bytesToMiB, formatMillicores } from '../../../utils/resources'; import { PodExecTerminalDialog } from '../../PodExecTerminal/PodExecTerminalDialog'; import { ResourceUtilization } from '../../ResourceUtilization'; import { PodLogsDialog, PodScope } from '../PodLogs'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../../translation'; const getContainerHealthChecks = ( containerSpec: IContainer, @@ -112,6 +114,7 @@ export const ContainerCard: FC = ({ containerStatus, containerMetrics, }: ContainerCardProps) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); const isPodExecTerminalEnabled = useIsPodExecTerminalEnabled(); // This should never be undefined @@ -183,13 +186,13 @@ export const ContainerCard: FC = ({ - Resource utilization + {t('podDrawer.resourceUtilization')} = ({ /> = ({ /> = ({ /> createStyles({ @@ -77,6 +79,7 @@ export const PodDrawer = ({ podAndErrors, open }: PodDrawerProps) => { const classes = useDrawerContentStyles(); const podMetrics = usePodMetrics(podAndErrors.cluster.name, podAndErrors.pod); const isPodDeleteEnabled = useIsPodDeleteEnabled(); + const { t } = useTranslationRef(kubernetesReactTranslationRef); return ( { {podMetrics && ( - Resource utilization + + {t('podDrawer.resourceUtilization')} + { = ({ containerScope, previous, }: PodLogsProps) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); const { value, error, loading } = usePodLogs({ containerScope, previous, @@ -72,8 +75,8 @@ export const PodLogs: FC = ({ (value.text === '' ? ( ) : ( diff --git a/plugins/kubernetes-react/src/components/Pods/PodLogs/PodLogsDialog.tsx b/plugins/kubernetes-react/src/components/Pods/PodLogs/PodLogsDialog.tsx index cb4ccfdf5d..59524e4ed1 100644 --- a/plugins/kubernetes-react/src/components/Pods/PodLogs/PodLogsDialog.tsx +++ b/plugins/kubernetes-react/src/components/Pods/PodLogs/PodLogsDialog.tsx @@ -18,6 +18,8 @@ import SubjectIcon from '@material-ui/icons/Subject'; import { KubernetesDialog } from '../../KubernetesDialog'; import { PodLogs } from './PodLogs'; import { ContainerScope } from './types'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../../translation'; /** * Props for PodLogsDialog @@ -34,17 +36,19 @@ export interface PodLogsDialogProps { * @public */ export const PodLogsDialog = ({ containerScope }: PodLogsDialogProps) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); return ( } - buttonText="Logs" + buttonText={t('podLogs.buttonText')} disabled={false} - title={`${containerScope.podName} - ${ - containerScope.containerName - } logs on cluster ${ - containerScope.cluster.title || containerScope.cluster.name - }`} + title={t('podLogs.titleTemplate', { + podName: containerScope.podName, + containerName: containerScope.containerName, + clusterName: + containerScope.cluster.title || containerScope.cluster.name, + })} > diff --git a/plugins/kubernetes-react/src/components/Pods/PodsTable.tsx b/plugins/kubernetes-react/src/components/Pods/PodsTable.tsx index 8d3c14d86a..ee2fce2822 100644 --- a/plugins/kubernetes-react/src/components/Pods/PodsTable.tsx +++ b/plugins/kubernetes-react/src/components/Pods/PodsTable.tsx @@ -30,6 +30,8 @@ import { Pod } from 'kubernetes-models/v1/Pod'; import type { V1Pod } from '@kubernetes/client-node'; import { usePodMetrics } from '../../hooks/usePodMetrics'; import Typography from '@material-ui/core/Typography'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../translation'; /** * @@ -63,22 +65,6 @@ export type PodsTablesProps = { children?: ReactNode; }; -const READY: TableColumn[] = [ - { - title: 'containers ready', - align: 'center', - render: containersReady, - width: 'auto', - }, - { - title: 'total restarts', - align: 'center', - render: totalRestarts, - type: 'numeric', - width: 'auto', - }, -]; - const PodDrawerTrigger = ({ pod }: { pod: Pod }) => { const errors = useMatchingErrors({ kind: 'Pod', @@ -98,9 +84,10 @@ const PodDrawerTrigger = ({ pod }: { pod: Pod }) => { const Cpu = ({ clusterName, pod }: { clusterName: string; pod: Pod }) => { const metrics = usePodMetrics(clusterName, pod); + const { t } = useTranslationRef(kubernetesReactTranslationRef); if (!metrics) { - return unknown; + return {t('podsTable.unknown')}; } return <>{podStatusToCpuUtil(metrics)}; @@ -108,9 +95,10 @@ const Cpu = ({ clusterName, pod }: { clusterName: string; pod: Pod }) => { const Memory = ({ clusterName, pod }: { clusterName: string; pod: Pod }) => { const metrics = usePodMetrics(clusterName, pod); + const { t } = useTranslationRef(kubernetesReactTranslationRef); if (!metrics) { - return unknown; + return {t('podsTable.unknown')}; } return <>{podStatusToMemoryUtil(metrics)}; @@ -123,26 +111,44 @@ const Memory = ({ clusterName, pod }: { clusterName: string; pod: Pod }) => { */ export const PodsTable = ({ pods, extraColumns = [] }: PodsTablesProps) => { const cluster = useContext(ClusterContext); + const { t } = useTranslationRef(kubernetesReactTranslationRef); + + const READY: TableColumn[] = [ + { + title: t('podsTable.columns.containersReady'), + align: 'center', + render: containersReady, + width: 'auto', + }, + { + title: t('podsTable.columns.totalRestarts'), + align: 'center', + render: totalRestarts, + type: 'numeric', + width: 'auto', + }, + ]; + const defaultColumns: TableColumn[] = [ { - title: 'ID', + title: t('podsTable.columns.id'), field: 'metadata.uid', hidden: true, }, { - title: 'name', + title: t('podsTable.columns.name'), highlight: true, render: (pod: Pod) => { return ; }, }, { - title: 'phase', - render: (pod: Pod) => pod.status?.phase ?? 'unknown', + title: t('podsTable.columns.phase'), + render: (pod: Pod) => pod.status?.phase ?? t('podsTable.unknown'), width: 'auto', }, { - title: 'status', + title: t('podsTable.columns.status'), render: containerStatuses, }, ]; @@ -154,14 +160,14 @@ export const PodsTable = ({ pods, extraColumns = [] }: PodsTablesProps) => { if (extraColumns.includes(RESOURCE_COLUMNS)) { const resourceColumns: TableColumn[] = [ { - title: 'CPU usage %', + title: t('podsTable.columns.cpuUsage'), render: (pod: Pod) => { return ; }, width: 'auto', }, { - title: 'Memory usage %', + title: t('podsTable.columns.memoryUsage'), render: (pod: Pod) => { return ; }, diff --git a/plugins/kubernetes-react/src/components/SecretsAccordions/SecretsAccordions.test.tsx b/plugins/kubernetes-react/src/components/SecretsAccordions/SecretsAccordions.test.tsx new file mode 100644 index 0000000000..1bdafa293b --- /dev/null +++ b/plugins/kubernetes-react/src/components/SecretsAccordions/SecretsAccordions.test.tsx @@ -0,0 +1,48 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { screen } from '@testing-library/react'; +import { SecretsAccordions } from './SecretsAccordions'; +import * as oneSecretsFixture from '../../__fixtures__/1-secrets.json'; +import * as twoSecretsFixture from '../../__fixtures__/2-secrets.json'; +import { renderInTestApp } from '@backstage/test-utils'; +import { kubernetesProviders } from '../../hooks/test-utils'; + +describe('SecretsAccordions', () => { + it('should render 1 secret', async () => { + const wrapper = kubernetesProviders(oneSecretsFixture, new Set()); + + await renderInTestApp(wrapper()); + + expect(screen.getByText('app-secret')).toBeInTheDocument(); + expect(screen.getByText('Secret')).toBeInTheDocument(); + expect(screen.getByText('namespace: default')).toBeInTheDocument(); + expect(screen.getByText('Data Count: 4')).toBeInTheDocument(); + }); + + it('should render 2 secrets', async () => { + const wrapper = kubernetesProviders(twoSecretsFixture, new Set()); + + await renderInTestApp(wrapper()); + + expect(screen.getByText('app-secret')).toBeInTheDocument(); + expect(screen.getByText('redis-secret')).toBeInTheDocument(); + expect(screen.getAllByText('Secret')).toHaveLength(2); + expect(screen.getAllByText('namespace: default')).toHaveLength(2); + expect(screen.getByText('Data Count: 4')).toBeInTheDocument(); + expect(screen.getByText('Data Count: 3')).toBeInTheDocument(); + }); +}); diff --git a/plugins/kubernetes-react/src/components/SecretsAccordions/SecretsAccordions.tsx b/plugins/kubernetes-react/src/components/SecretsAccordions/SecretsAccordions.tsx new file mode 100644 index 0000000000..4271a2af8b --- /dev/null +++ b/plugins/kubernetes-react/src/components/SecretsAccordions/SecretsAccordions.tsx @@ -0,0 +1,108 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { useContext } from 'react'; +import Accordion from '@material-ui/core/Accordion'; +import AccordionDetails from '@material-ui/core/AccordionDetails'; +import AccordionSummary from '@material-ui/core/AccordionSummary'; +import Grid from '@material-ui/core/Grid'; +import Typography from '@material-ui/core/Typography'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import type { V1Secret } from '@kubernetes/client-node'; +import { SecretsDrawer } from './SecretsDrawer.tsx'; +import { GroupedResponsesContext } from '../../hooks'; +import { StructuredMetadataTable } from '@backstage/core-components'; + +type SecretSummaryProps = { + secret: V1Secret; +}; + +const SecretSummary = ({ secret }: SecretSummaryProps) => { + return ( + + + + + + + + Data Count: {secret.data ? Object.keys(secret.data).length : 0} + + + + ); +}; + +type SecretsCardProps = { + secret: V1Secret; +}; + +const SecretCard = ({ secret }: SecretsCardProps) => { + const metadata: any = {}; + + metadata.data = secret.data; + + return ( + + ); +}; + +export type SecretsAccordionsProps = {}; + +type SecretsAccordionProps = { + secret: V1Secret; +}; + +const SecretsAccordion = ({ secret }: SecretsAccordionProps) => { + return ( + + }> + + + + + + + ); +}; + +export const SecretsAccordions = ({}: SecretsAccordionsProps) => { + const groupedResponses = useContext(GroupedResponsesContext); + return ( + + {groupedResponses.secrets.map((secret, i) => ( + + + + ))} + + ); +}; diff --git a/plugins/kubernetes-react/src/components/SecretsAccordions/SecretsDrawer.test.tsx b/plugins/kubernetes-react/src/components/SecretsAccordions/SecretsDrawer.test.tsx new file mode 100644 index 0000000000..6dd1230ee4 --- /dev/null +++ b/plugins/kubernetes-react/src/components/SecretsAccordions/SecretsDrawer.test.tsx @@ -0,0 +1,37 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import * as oneSecretsFixture from '../../__fixtures__/1-secrets.json'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { SecretsDrawer } from './SecretsDrawer'; +import { kubernetesClusterLinkFormatterApiRef } from '../../api'; + +describe('SecretsDrawer', () => { + it('should render secret drawer', async () => { + const { getByText, getAllByText } = await renderInTestApp( + + + , + ); + + expect(getAllByText('app-secret')).toHaveLength(3); + expect(getAllByText('Secret')).toHaveLength(3); + expect(getByText('YAML')).toBeInTheDocument(); + expect(getByText('namespace: default')).toBeInTheDocument(); + }); +}); diff --git a/plugins/kubernetes-react/src/components/SecretsAccordions/SecretsDrawer.tsx b/plugins/kubernetes-react/src/components/SecretsAccordions/SecretsDrawer.tsx new file mode 100644 index 0000000000..72df6bdae9 --- /dev/null +++ b/plugins/kubernetes-react/src/components/SecretsAccordions/SecretsDrawer.tsx @@ -0,0 +1,64 @@ +/* + * 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 { KubernetesStructuredMetadataTableDrawer } from '../KubernetesDrawer'; +import Typography from '@material-ui/core/Typography'; +import Grid from '@material-ui/core/Grid'; +import Chip from '@material-ui/core/Chip'; +import type { V1Secret } from '@kubernetes/client-node'; + +export const SecretsDrawer = ({ + secret, + expanded, +}: { + secret: V1Secret; + expanded?: boolean; +}) => { + const namespace = secret.metadata?.namespace; + return ( + { + return secretObject || {}; + }} + > + + + + {secret.metadata?.name ?? 'unknown object'} + + + + + Secret + + + {namespace && ( + + + + )} + + + ); +}; diff --git a/plugins/kubernetes-react/src/components/SecretsAccordions/index.ts b/plugins/kubernetes-react/src/components/SecretsAccordions/index.ts new file mode 100644 index 0000000000..461537fe72 --- /dev/null +++ b/plugins/kubernetes-react/src/components/SecretsAccordions/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './SecretsAccordions.tsx'; diff --git a/plugins/kubernetes-react/src/components/ServicesAccordions/ServiceDrawer.tsx b/plugins/kubernetes-react/src/components/ServicesAccordions/ServiceDrawer.tsx index a9f7b7b5ad..d9d08d04cb 100644 --- a/plugins/kubernetes-react/src/components/ServicesAccordions/ServiceDrawer.tsx +++ b/plugins/kubernetes-react/src/components/ServicesAccordions/ServiceDrawer.tsx @@ -19,6 +19,8 @@ import { KubernetesStructuredMetadataTableDrawer } from '../KubernetesDrawer'; import Typography from '@material-ui/core/Typography'; import Grid from '@material-ui/core/Grid'; import Chip from '@material-ui/core/Chip'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../translation'; export const ServiceDrawer = ({ service, @@ -27,6 +29,7 @@ export const ServiceDrawer = ({ service: V1Service; expanded?: boolean; }) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); const namespace = service.metadata?.namespace; return ( {namespace && ( - + )} diff --git a/plugins/kubernetes-react/src/components/StatefulSetsAccordions/StatefulSetDrawer.tsx b/plugins/kubernetes-react/src/components/StatefulSetsAccordions/StatefulSetDrawer.tsx index 7b7a2b7bb2..ebb446b40c 100644 --- a/plugins/kubernetes-react/src/components/StatefulSetsAccordions/StatefulSetDrawer.tsx +++ b/plugins/kubernetes-react/src/components/StatefulSetsAccordions/StatefulSetDrawer.tsx @@ -21,6 +21,8 @@ import { renderCondition } from '../../utils/pod'; import Typography from '@material-ui/core/Typography'; import Grid from '@material-ui/core/Grid'; import Chip from '@material-ui/core/Chip'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../translation'; export const StatefulSetDrawer = ({ statefulset, @@ -29,6 +31,7 @@ export const StatefulSetDrawer = ({ statefulset: V1StatefulSet; expanded?: boolean; }) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); const namespace = statefulset.metadata?.namespace; return ( {namespace && ( - + )} diff --git a/plugins/kubernetes-react/src/components/StatefulSetsAccordions/StatefulSetsAccordions.tsx b/plugins/kubernetes-react/src/components/StatefulSetsAccordions/StatefulSetsAccordions.tsx index e3ae9776bd..e1a33474f2 100644 --- a/plugins/kubernetes-react/src/components/StatefulSetsAccordions/StatefulSetsAccordions.tsx +++ b/plugins/kubernetes-react/src/components/StatefulSetsAccordions/StatefulSetsAccordions.tsx @@ -36,6 +36,8 @@ import { } from '../../hooks'; import { StatusError, StatusOK } from '@backstage/core-components'; import { READY_COLUMNS, RESOURCE_COLUMNS } from '../Pods/PodsTable'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesReactTranslationRef } from '../../translation'; type StatefulSetsAccordionsProps = { children?: ReactNode; @@ -62,6 +64,7 @@ const StatefulSetSummary = ({ numberOfPodsWithErrors, hpa, }: StatefulSetSummaryProps) => { + const { t } = useTranslationRef(kubernetesReactTranslationRef); const specCpuUtil = hpa?.spec?.metrics?.find( metric => metric.type === 'Resource' && metric.resource?.name === 'cpu', )?.resource?.target.averageUtilization; @@ -94,18 +97,18 @@ const StatefulSetSummary = ({ > - min replicas {hpa.spec?.minReplicas ?? '?'} / max replicas{' '} - {hpa.spec?.maxReplicas ?? '?'} + {t('hpa.minReplicas')} {hpa.spec?.minReplicas ?? '?'} /{' '} + {t('hpa.maxReplicas')} {hpa.spec?.maxReplicas ?? '?'} - current CPU usage: {cpuUtil ?? '?'}% + {t('hpa.currentCpuUsage')} {cpuUtil ?? '?'}% - target CPU usage: {specCpuUtil ?? '?'}% + {t('hpa.targetCpuUsage')} {specCpuUtil ?? '?'}% @@ -122,16 +125,15 @@ const StatefulSetSummary = ({ spacing={0} > - {numberOfCurrentPods} pods + {t('pods.pods', { count: numberOfCurrentPods })} {numberOfPodsWithErrors > 0 ? ( - {numberOfPodsWithErrors} pod - {numberOfPodsWithErrors > 1 ? 's' : ''} with errors + {t('cluster.podsWithErrors', { count: numberOfPodsWithErrors })} ) : ( - No pods with errors + {t('cluster.noPodsWithErrors')} )} diff --git a/plugins/kubernetes-react/src/hooks/GroupedResponses.ts b/plugins/kubernetes-react/src/hooks/GroupedResponses.ts index 1dbf16c57e..750ee58846 100644 --- a/plugins/kubernetes-react/src/hooks/GroupedResponses.ts +++ b/plugins/kubernetes-react/src/hooks/GroupedResponses.ts @@ -28,6 +28,7 @@ export const GroupedResponsesContext = createContext({ daemonSets: [], services: [], configMaps: [], + secrets: [], horizontalPodAutoscalers: [], ingresses: [], jobs: [], diff --git a/plugins/kubernetes-react/src/hooks/useIsPodExecTerminalSupported.test.ts b/plugins/kubernetes-react/src/hooks/useIsPodExecTerminalSupported.test.ts index c4df347c6f..26ba8f5e91 100644 --- a/plugins/kubernetes-react/src/hooks/useIsPodExecTerminalSupported.test.ts +++ b/plugins/kubernetes-react/src/hooks/useIsPodExecTerminalSupported.test.ts @@ -55,7 +55,7 @@ describe('useIsPodExecTerminalSupported', () => { }, { condition: 'AuthProvider is google', - returnValue: false, + returnValue: true, testClusters: [{ authProvider: 'google' }], }, { diff --git a/plugins/kubernetes-react/src/hooks/useIsPodExecTerminalSupported.ts b/plugins/kubernetes-react/src/hooks/useIsPodExecTerminalSupported.ts index 5795b900ae..06c1a8808f 100644 --- a/plugins/kubernetes-react/src/hooks/useIsPodExecTerminalSupported.ts +++ b/plugins/kubernetes-react/src/hooks/useIsPodExecTerminalSupported.ts @@ -34,8 +34,8 @@ export const useIsPodExecTerminalSupported = (): AsyncState => { } const { authProvider } = clusters[0]; - const isClientAuthProvider = ['aks', 'google', 'oidc'].some( - authProviderName => authProvider.includes(authProviderName), + const isClientAuthProvider = ['aks', 'oidc'].some(authProviderName => + authProvider.includes(authProviderName), ); return !isClientAuthProvider; diff --git a/plugins/kubernetes-react/src/translation.ts b/plugins/kubernetes-react/src/translation.ts index 5b8a26b0d5..0420cc04bf 100644 --- a/plugins/kubernetes-react/src/translation.ts +++ b/plugins/kubernetes-react/src/translation.ts @@ -20,10 +20,130 @@ import { createTranslationRef } from '@backstage/core-plugin-api/alpha'; export const kubernetesReactTranslationRef = createTranslationRef({ id: 'kubernetes-react', messages: { + cluster: { + label: 'Cluster', + pods: 'pods', + pods_one: '{{count}} pod', + pods_other: '{{count}} pods', + podsWithErrors: 'pods with errors', + podsWithErrors_one: '{{count}} pod with errors', + podsWithErrors_other: '{{count}} pods with errors', + noPodsWithErrors: 'No pods with errors', + }, + podsTable: { + columns: { + id: 'ID', + name: 'name', + phase: 'phase', + status: 'status', + containersReady: 'containers ready', + totalRestarts: 'total restarts', + cpuUsage: 'CPU usage %', + memoryUsage: 'Memory usage %', + }, + unknown: 'unknown', + status: { + running: 'Running', + ok: 'OK', + }, + }, + errorPanel: { + title: 'There was a problem retrieving Kubernetes objects', + message: + 'There was a problem retrieving some Kubernetes resources for the entity: {{entityName}}. This could mean that the Error Reporting card is not completely accurate.', + errorsLabel: 'Errors', + clusterLabel: 'Cluster', + clusterLabelValue: 'Cluster: {{cluster}}', + fetchError: + 'Error communicating with Kubernetes: {{errorType}}, message: {{message}}', + resourceError: + "Error fetching Kubernetes resource: '{{resourcePath}}', error: {{errorType}}, status code: {{statusCode}}", + }, + fixDialog: { + helpButton: 'Help', + title: '{{podName}} - {{errorType}}', + detectedError: 'Detected error:', + causeExplanation: 'Cause explanation:', + fix: 'Fix:', + crashLogs: 'Crash logs:', + events: 'Events:', + openDocs: 'Open docs', + ariaLabels: { + fixIssue: 'fix issue', + close: 'close', + }, + }, podDrawer: { buttons: { delete: 'Delete Pod', }, + cpuRequests: 'CPU requests', + cpuLimits: 'CPU limits', + memoryRequests: 'Memory requests', + memoryLimits: 'Memory limits', + resourceUtilization: 'Resource utilization', + }, + hpa: { + minReplicas: 'min replicas', + maxReplicas: 'max replicas', + replicasSummary: 'min replicas {{min}} / max replicas {{max}}', + currentCpuUsage: 'current CPU usage:', + currentCpuUsageLabel: 'current CPU usage: {{value}}%', + targetCpuUsage: 'target CPU usage:', + targetCpuUsageLabel: 'target CPU usage: {{value}}%', + }, + pods: { + pods_one: '{{count}} pod', + pods_other: '{{count}} pods', + }, + errorReporting: { + title: 'Error Reporting', + columns: { + cluster: 'cluster', + namespace: 'namespace', + kind: 'kind', + name: 'name', + messages: 'messages', + }, + }, + podLogs: { + title: 'No logs emitted', + description: 'No logs were emitted by the container', + buttonText: 'Logs', + buttonAriaLabel: 'get logs', + titleTemplate: + '{{podName}} - {{containerName}} logs on cluster {{clusterName}}', + }, + podExecTerminal: { + buttonText: 'Terminal', + buttonAriaLabel: 'open terminal', + titleTemplate: + '{{podName}} - {{containerName}} terminal shell on cluster {{clusterName}}', + }, + events: { + noEventsFound: 'No events found', + eventTooltip: '{{eventType}} event', + firstEvent: 'First event {{timeAgo}} (count: {{count}})', + }, + kubernetesDrawer: { + closeDrawer: 'Close the drawer', + yaml: 'YAML', + managedFields: 'Managed Fields', + unknownName: 'unknown name', + }, + linkErrorPanel: { + title: + 'There was a problem formatting the link to the Kubernetes dashboard', + message: + "Could not format the link to the dashboard of your cluster named '{{clusterName}}'. Its dashboardApp property has been set to '{{dashboardApp}}.'", + errorsLabel: 'Errors:', + }, + namespace: { + label: 'namespace:', + labelWithValue: 'namespace: {{namespace}}', + }, + kubernetesDialog: { + closeAriaLabel: 'close', }, }, }); diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index 7659669cfd..3aeaa369fa 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,46 @@ # @backstage/plugin-kubernetes +## 0.12.14 + +### Patch Changes + +- f15d5f1: add missing i18n support for kubernetes and kubernetes-react +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/plugin-kubernetes-react@0.5.14 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-kubernetes-common@0.9.9 + - @backstage/core-plugin-api@1.12.1 + - @backstage/plugin-permission-react@0.4.39 + +## 0.12.14-next.1 + +### Patch Changes + +- 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 + +## 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 + ## 0.12.13 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index a003601bbe..33692f5886 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes", - "version": "0.12.13", + "version": "0.12.14", "description": "A Backstage plugin that integrates towards Kubernetes", "backstage": { "role": "frontend-plugin", @@ -59,7 +59,6 @@ }, "dependencies": { "@backstage/catalog-model": "workspace:^", - "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", diff --git a/plugins/kubernetes/report-alpha.api.md b/plugins/kubernetes/report-alpha.api.md index 571e09d546..9b847b862a 100644 --- a/plugins/kubernetes/report-alpha.api.md +++ b/plugins/kubernetes/report-alpha.api.md @@ -14,8 +14,9 @@ import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; -import { RouteRef } from '@backstage/frontend-plugin-api'; -import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +import { RouteRef } from '@backstage/core-plugin-api'; +import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; // @public (undocumented) const _default: OverridableFrontendPlugin< @@ -103,7 +104,7 @@ const _default: OverridableFrontendPlugin< | ExtensionDataRef | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; @@ -140,7 +141,7 @@ const _default: OverridableFrontendPlugin< defaultGroup?: [Error: `Use the 'group' param instead`]; group?: keyof defaultEntityContentGroups | (string & {}); loader: () => Promise; - routeRef?: RouteRef; + routeRef?: RouteRef_2; filter?: string | EntityPredicate | ((entity: Entity) => boolean); }; }>; @@ -157,7 +158,7 @@ const _default: OverridableFrontendPlugin< | ExtensionDataRef | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; @@ -168,7 +169,7 @@ const _default: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; loader: () => Promise; - routeRef?: RouteRef; + routeRef?: RouteRef_2; }; }>; } @@ -179,6 +180,10 @@ export default _default; export const kubernetesTranslationRef: TranslationRef< 'kubernetes', { + readonly 'entityContent.title': 'Kubernetes'; + readonly 'kubernetesContentPage.title': 'Your Clusters'; + readonly 'kubernetesContentPage.emptyState.title': 'No Kubernetes resources'; + readonly 'kubernetesContentPage.emptyState.description': 'No resources on any known clusters for {{entityName}}'; readonly 'kubernetesContentPage.permissionAlert.message': "To view Kubernetes objects, contact your portal administrator to give you the 'kubernetes.clusters.read' and 'kubernetes.resources.read' permission."; readonly 'kubernetesContentPage.permissionAlert.title': 'Permission required'; } diff --git a/plugins/kubernetes/src/KubernetesContent.tsx b/plugins/kubernetes/src/KubernetesContent.tsx index a8d62a5a3d..4f7c7e73e2 100644 --- a/plugins/kubernetes/src/KubernetesContent.tsx +++ b/plugins/kubernetes/src/KubernetesContent.tsx @@ -31,6 +31,8 @@ import { } from '@backstage/plugin-kubernetes-common'; import { EmptyState, Progress } from '@backstage/core-components'; import { RequireKubernetesPermissions } from './RequireKubernetesPermissions'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { kubernetesTranslationRef } from './alpha/translation'; type KubernetesContentProps = { entity: Entity; @@ -46,6 +48,7 @@ export const KubernetesContent = ({ entity, refreshIntervalMs, ); + const { t } = useTranslationRef(kubernetesTranslationRef); const clusters = kubernetesObjects?.items.map(item => item.cluster) ?? []; @@ -97,7 +100,9 @@ export const KubernetesContent = ({ /> - Your Clusters + + {t('kubernetesContentPage.title')} + {kubernetesObjects?.items.length <= 0 && ( @@ -111,8 +116,11 @@ export const KubernetesContent = ({ diff --git a/plugins/kubernetes/src/alpha/entityContents.tsx b/plugins/kubernetes/src/alpha/entityContents.tsx index d75893c6e4..0a85cadaed 100644 --- a/plugins/kubernetes/src/alpha/entityContents.tsx +++ b/plugins/kubernetes/src/alpha/entityContents.tsx @@ -14,7 +14,6 @@ * limitations under the License. */ -import { compatWrapper } from '@backstage/core-compat-api'; import { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha'; import { isKubernetesAvailable } from '../Router'; @@ -26,8 +25,6 @@ export const entityKubernetesContent = EntityContentBlueprint.make({ group: 'deployment', filter: isKubernetesAvailable, loader: () => - import('./KubernetesContentPage').then(m => - compatWrapper(), - ), + import('./KubernetesContentPage').then(m => ), }, }); diff --git a/plugins/kubernetes/src/alpha/pages.tsx b/plugins/kubernetes/src/alpha/pages.tsx index 6a2d98ddcc..3069d25cd6 100644 --- a/plugins/kubernetes/src/alpha/pages.tsx +++ b/plugins/kubernetes/src/alpha/pages.tsx @@ -14,20 +14,13 @@ * limitations under the License. */ -import { PageBlueprint } from '@backstage/frontend-plugin-api'; // Add this line to import React -import { - compatWrapper, - convertLegacyRouteRef, -} from '@backstage/core-compat-api'; +import { PageBlueprint } from '@backstage/frontend-plugin-api'; import { rootCatalogKubernetesRouteRef } from '../plugin'; export const kubernetesPage = PageBlueprint.make({ params: { path: '/kubernetes', - // you can reuse the existing routeRef - // by wrapping into the convertLegacyRouteRef. - routeRef: convertLegacyRouteRef(rootCatalogKubernetesRouteRef), - // these inputs usually match the props required by the component. - loader: () => import('../Router').then(m => compatWrapper()), + routeRef: rootCatalogKubernetesRouteRef, + loader: () => import('../Router').then(m => ), }, }); diff --git a/plugins/kubernetes/src/alpha/plugin.tsx b/plugins/kubernetes/src/alpha/plugin.tsx index db7b2439bf..fdca467886 100644 --- a/plugins/kubernetes/src/alpha/plugin.tsx +++ b/plugins/kubernetes/src/alpha/plugin.tsx @@ -14,7 +14,6 @@ * limitations under the License. */ -import { convertLegacyRouteRefs } from '@backstage/core-compat-api'; import { createFrontendPlugin } from '@backstage/frontend-plugin-api'; import { kubernetesPage } from './pages'; import { entityKubernetesContent } from './entityContents'; @@ -37,5 +36,5 @@ export default createFrontendPlugin({ kubernetesAuthProvidersApi, kubernetesClusterLinkFormatterApi, ], - routes: convertLegacyRouteRefs({ kubernetes: rootCatalogKubernetesRouteRef }), + routes: { kubernetes: rootCatalogKubernetesRouteRef }, }); diff --git a/plugins/kubernetes/src/alpha/translation.ts b/plugins/kubernetes/src/alpha/translation.ts index 7f9704af84..238bd3b084 100644 --- a/plugins/kubernetes/src/alpha/translation.ts +++ b/plugins/kubernetes/src/alpha/translation.ts @@ -25,6 +25,14 @@ export const kubernetesTranslationRef = createTranslationRef({ message: "To view Kubernetes objects, contact your portal administrator to give you the 'kubernetes.clusters.read' and 'kubernetes.resources.read' permission.", }, + title: 'Your Clusters', + emptyState: { + title: 'No Kubernetes resources', + description: 'No resources on any known clusters for {{entityName}}', + }, + }, + entityContent: { + title: 'Kubernetes', }, }, }); diff --git a/plugins/mcp-actions-backend/CHANGELOG.md b/plugins/mcp-actions-backend/CHANGELOG.md index a119e39138..49fba49ade 100644 --- a/plugins/mcp-actions-backend/CHANGELOG.md +++ b/plugins/mcp-actions-backend/CHANGELOG.md @@ -1,5 +1,44 @@ # @backstage/plugin-mcp-actions-backend +## 0.1.6 + +### Patch Changes + +- e83e038: Added `@cfworker/json-schema` as a dependency to this package part of the `@modelcontextprotocol/sdk` bump as it's required in the types +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 79ef471: Clarify error handling in readme and update handleError.ts to include all backstage/errors +- Updated dependencies + - @backstage/backend-defaults@0.14.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## 0.1.6-next.1 + +### Patch Changes + +- e83e038: Added `@cfworker/json-schema` as a dependency to this package part of the `@modelcontextprotocol/sdk` bump as it's required in the types +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## 0.1.6-next.0 + +### Patch Changes + +- 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 + ## 0.1.5 ### Patch Changes diff --git a/plugins/mcp-actions-backend/README.md b/plugins/mcp-actions-backend/README.md index d81d0207c5..39ec169dec 100644 --- a/plugins/mcp-actions-backend/README.md +++ b/plugins/mcp-actions-backend/README.md @@ -71,6 +71,31 @@ export const myPlugin = createBackendPlugin({ }); ``` +### Error Handling + +When errors are thrown from MCP actions, the backend will handle and surface error message for any error from `@backstage/errors`. Unknown errors will be handled by `@modelcontextprotocol/sdk`'s default error handling, which may result in a generic `500 Server Error` being returned. As a result, we recommend using errors from `@backstage/errors` when applicable. + +See https://backstage.io/docs/reference/errors/ for a full list of supported errors. + +When writing MCP tools, use the appropriate error from `@backstage/errors` when applicable: + +```ts +action: async ({ input }) => { + // ... get current user and some resource + + if (!resource) { + throw new NotFoundError(`Resource ${input.id} not found`); + } + + // Check if the user has permissions to access/use the resource + if (!hasPermission(user, resource)) { + throw new NotAllowedError( + `user does not have sufficient permissions for ${resource}`, + ); + } +}; +``` + ### Authentication Configuration By default, the Backstage backend requires authentication for all requests. diff --git a/plugins/mcp-actions-backend/package.json b/plugins/mcp-actions-backend/package.json index 24ac649f5a..327e308d8e 100644 --- a/plugins/mcp-actions-backend/package.json +++ b/plugins/mcp-actions-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-mcp-actions-backend", - "version": "0.1.5", + "version": "0.1.6", "backstage": { "role": "backend-plugin", "pluginId": "mcp-actions", @@ -40,8 +40,9 @@ "@backstage/errors": "workspace:^", "@backstage/plugin-catalog-node": "workspace:^", "@backstage/types": "workspace:^", - "@modelcontextprotocol/sdk": "^1.12.3", - "express": "^4.17.1", + "@cfworker/json-schema": "^4.1.1", + "@modelcontextprotocol/sdk": "^1.24.3", + "express": "^4.22.0", "express-promise-router": "^4.1.0", "zod": "^3.22.4" }, diff --git a/plugins/mcp-actions-backend/src/plugin.test.ts b/plugins/mcp-actions-backend/src/plugin.test.ts index ace988cfc1..47dd89c9fc 100644 --- a/plugins/mcp-actions-backend/src/plugin.test.ts +++ b/plugins/mcp-actions-backend/src/plugin.test.ts @@ -20,7 +20,7 @@ import { createBackendPlugin } from '@backstage/backend-plugin-api'; import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'; -import { ListToolsResultSchema } from '@modelcontextprotocol/sdk/types'; +import { ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js'; describe('Mcp Backend', () => { const mockPluginWithActions = createBackendPlugin({ diff --git a/plugins/mcp-actions-backend/src/services/handleErrors.ts b/plugins/mcp-actions-backend/src/services/handleErrors.ts index 67d92664eb..5266c80e75 100644 --- a/plugins/mcp-actions-backend/src/services/handleErrors.ts +++ b/plugins/mcp-actions-backend/src/services/handleErrors.ts @@ -32,6 +32,7 @@ const knownErrors = new Set([ 'NotModifiedError', 'NotImplementedError', 'ResponseError', + 'ServiceUnavailableError', ]); // Extracts the cause error, if the provided error is `ResponseError` or diff --git a/plugins/mui-to-bui/CHANGELOG.md b/plugins/mui-to-bui/CHANGELOG.md index 6b3daef95c..330f9edbf6 100644 --- a/plugins/mui-to-bui/CHANGELOG.md +++ b/plugins/mui-to-bui/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-mui-to-bui +## 0.2.2 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.10.0 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.10.0-next.1 + +## 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 + ## 0.2.1 ### Patch Changes diff --git a/plugins/mui-to-bui/package.json b/plugins/mui-to-bui/package.json index ca66765d79..c40afd4283 100644 --- a/plugins/mui-to-bui/package.json +++ b/plugins/mui-to-bui/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-mui-to-bui", - "version": "0.2.1", + "version": "0.2.2", "backstage": { "role": "frontend-plugin", "pluginId": "mui-to-bui", @@ -35,7 +35,6 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/core-compat-api": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", "@backstage/theme": "workspace:^", diff --git a/plugins/mui-to-bui/report.api.md b/plugins/mui-to-bui/report.api.md index 519f0c1bc7..db1d49f828 100644 --- a/plugins/mui-to-bui/report.api.md +++ b/plugins/mui-to-bui/report.api.md @@ -10,8 +10,8 @@ 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'; -import { RouteRef as RouteRef_2 } from '@backstage/core-plugin-api'; +import { RouteRef } from '@backstage/core-plugin-api'; +import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; // @public (undocumented) export const BuiThemerPage: () => JSX_3.Element; @@ -19,7 +19,7 @@ export const BuiThemerPage: () => JSX_3.Element; // @public (undocumented) export const buiThemerPlugin: BackstagePlugin< { - root: RouteRef_2; + root: RouteRef; }, {} >; @@ -44,7 +44,7 @@ const _default: OverridableFrontendPlugin< | ExtensionDataRef | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; @@ -55,7 +55,7 @@ const _default: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; loader: () => Promise; - routeRef?: RouteRef; + routeRef?: RouteRef_2; }; }>; } diff --git a/plugins/mui-to-bui/src/components/BuiThemerPage/convertMuiToBuiTheme.test.ts b/plugins/mui-to-bui/src/components/BuiThemerPage/convertMuiToBuiTheme.test.ts index 6ba3f7d2d9..48bb318a71 100644 --- a/plugins/mui-to-bui/src/components/BuiThemerPage/convertMuiToBuiTheme.test.ts +++ b/plugins/mui-to-bui/src/components/BuiThemerPage/convertMuiToBuiTheme.test.ts @@ -59,7 +59,7 @@ describe('convertMuiToBuiTheme', () => { // Border radius tokens are only generated when radius is 0 expect(result.css).not.toContain('--bui-radius-3:'); // Background default maps to surface-2 - expect(result.css).toContain('--bui-bg: #f5f5f5;'); + expect(result.css).toContain('--bui-bg-surface-0: #f5f5f5;'); expect(result.css).toContain('--bui-bg-surface-2: #f5f5f5;'); expect(result.css).toContain('--bui-bg-surface-1: #ffffff;'); expect(result.css).toContain('--bui-fg-primary: #000000;'); @@ -98,7 +98,7 @@ describe('convertMuiToBuiTheme', () => { expect(result.css).toContain("[data-theme-mode='dark'] {"); // Background default maps to surface-2 in dark mode as well - expect(result.css).toContain('--bui-bg: #121212;'); + expect(result.css).toContain('--bui-bg-surface-0: #121212;'); expect(result.css).toContain('--bui-bg-surface-2: #121212;'); expect(result.css).toContain('--bui-bg-surface-1: #1e1e1e;'); expect(result.css).toContain('--bui-fg-primary: #ffffff;'); diff --git a/plugins/mui-to-bui/src/components/BuiThemerPage/convertMuiToBuiTheme.ts b/plugins/mui-to-bui/src/components/BuiThemerPage/convertMuiToBuiTheme.ts index 1b8528922b..029d70cec7 100644 --- a/plugins/mui-to-bui/src/components/BuiThemerPage/convertMuiToBuiTheme.ts +++ b/plugins/mui-to-bui/src/components/BuiThemerPage/convertMuiToBuiTheme.ts @@ -110,9 +110,10 @@ function generateBuiVariables(theme: Mui5Theme): Record { // Generate surface colors Object.entries({ - '': palette.background.default, + 'surface-0': palette.background.default, 'surface-1': palette.background.paper, 'surface-2': palette.background.default, + 'surface-3': palette.background.default, solid: palette.primary.main, 'solid-hover': blend(palette.primary.main, palette.primary.dark, 0.5), 'solid-pressed': palette.primary.dark, @@ -125,7 +126,7 @@ function generateBuiVariables(theme: Mui5Theme): Record { warning: palette.warning.light, success: palette.success.light, }).forEach(([key, value]) => { - styleObject[`--bui-bg${key ? `-${key}` : ''}`] = value; + styleObject[`--bui-bg-${key}`] = value; }); // Border colors diff --git a/plugins/mui-to-bui/src/plugin.tsx b/plugins/mui-to-bui/src/plugin.tsx index aca19c9978..ccebe6e379 100644 --- a/plugins/mui-to-bui/src/plugin.tsx +++ b/plugins/mui-to-bui/src/plugin.tsx @@ -14,10 +14,6 @@ * limitations under the License. */ -import { - convertLegacyRouteRef, - convertLegacyRouteRefs, -} from '@backstage/core-compat-api'; import { createPlugin, createRoutableExtension, @@ -57,11 +53,11 @@ export default createFrontendPlugin({ path: '/mui-to-bui', loader: () => import('./components/BuiThemerPage').then(m => ), - routeRef: convertLegacyRouteRef(rootRouteRef), + routeRef: rootRouteRef, }, }), ], - routes: convertLegacyRouteRefs({ + routes: { root: rootRouteRef, - }), + }, }); diff --git a/plugins/notifications-backend-module-email/CHANGELOG.md b/plugins/notifications-backend-module-email/CHANGELOG.md index 5ad069eab5..21cb61c4c4 100644 --- a/plugins/notifications-backend-module-email/CHANGELOG.md +++ b/plugins/notifications-backend-module-email/CHANGELOG.md @@ -1,5 +1,64 @@ # @backstage/plugin-notifications-backend-module-email +## 0.3.17 + +### Patch Changes + +- a5d5b3a: SES config for the notification email processor now supports utilizing an ARN for the SES identity when sending an email after the SES SDK V2 update. + + The `sesConfig.fromArn` will set the `fromEmailAddressIdentityArn` option for the SES `SendEmailCommand`. The `sesConfig.sourceArn` field is removed since no equivalent option is available in the send email command options. Setting `sesConfig.sourceArn` will have no effect and log a warning. Example changes: + + ```diff + notifications: + processors: + email: + transportConfig: + transport: "ses" + region: "us-west-2" + sender: "sender@mycompany.com" + replyTo: "no-reply@mycompany.com" + sesConfig: + - sourceArn: "arn:aws:ses:us-west-2:123456789012:identity/example.com" + fromArn: "arn:aws:ses:us-west-2:123456789012:identity/example.com" + ``` + +- b267aea: Updated dependency `@types/nodemailer` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-notifications-node@0.2.22 + +## 0.3.17-next.1 + +### Patch Changes + +- b267aea: Updated dependency `@types/nodemailer` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/integration-aws-node@0.1.19 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/plugin-notifications-node@0.2.22-next.1 + +## 0.3.17-next.0 + +### Patch Changes + +- 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 + ## 0.3.16 ### Patch Changes diff --git a/plugins/notifications-backend-module-email/README.md b/plugins/notifications-backend-module-email/README.md index c1ead48876..248bb2c501 100644 --- a/plugins/notifications-backend-module-email/README.md +++ b/plugins/notifications-backend-module-email/README.md @@ -81,7 +81,6 @@ notifications: receiver: 'users' # Optional SES config # sesConfig: - # sourceArn: 'arn:aws:ses:us-west-2:123456789012:identity/example.com' # fromArn: 'arn:aws:ses:us-west-2:123456789012:identity/example.com' # configurationSetName: 'custom-config' # How many emails to send concurrently, defaults to 2 diff --git a/plugins/notifications-backend-module-email/config.d.ts b/plugins/notifications-backend-module-email/config.d.ts index 7116826c5d..55a751f8fa 100644 --- a/plugins/notifications-backend-module-email/config.d.ts +++ b/plugins/notifications-backend-module-email/config.d.ts @@ -136,10 +136,6 @@ export interface Config { * Optional SES config for mail options. Allows for delegated sender */ sesConfig?: { - /** - * ARN of the identity to use as the source of the email - */ - sourceArn?: string; /** * ARN of the identity to use for the "From"/sender address of the email */ diff --git a/plugins/notifications-backend-module-email/package.json b/plugins/notifications-backend-module-email/package.json index 2907ffbb95..b1f2831758 100644 --- a/plugins/notifications-backend-module-email/package.json +++ b/plugins/notifications-backend-module-email/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend-module-email", - "version": "0.3.16", + "version": "0.3.17", "description": "The email backend module for the notifications plugin.", "backstage": { "role": "backend-plugin-module", @@ -53,7 +53,7 @@ "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@types/nodemailer": "^6.4.14" + "@types/nodemailer": "^7.0.0" }, "configSchema": "config.d.ts" } diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts index ebe1b90f35..ad0817521f 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts @@ -497,9 +497,13 @@ describe('NotificationsEmailProcessor', () => { text: 'https://example.org/notifications', to: 'mock@backstage.io', ses: { - SourceArn: 'arn:aws:ses:us-west-2:123456789012:identity/example.com', - FromArn: 'arn:aws:ses:us-west-2:123456789012:identity/example.com', + FromEmailAddressIdentityArn: + 'arn:aws:ses:us-west-2:123456789012:identity/example.com', }, }); + + expect(logger.warn).toHaveBeenCalledWith( + 'sourceArn is not supported in SESv2 and will be ignored', + ); }); }); diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts index eadfe7747e..e0e27232c2 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts @@ -45,6 +45,7 @@ import { DefaultAwsCredentialsManager } from '@backstage/integration-aws-node'; import { NotificationTemplateRenderer } from '../extensions'; import Mail from 'nodemailer/lib/mailer'; import pThrottle from 'p-throttle'; +import { SendEmailCommandInput } from '@aws-sdk/client-sesv2'; export class NotificationsEmailProcessor implements NotificationProcessor { private transporter: any; @@ -53,6 +54,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { private readonly sender: string; private readonly replyTo?: string; private readonly sesConfig?: Config; + private readonly sesOptions?: Partial; private readonly cacheTtl: number; private readonly concurrencyLimit: number; private readonly throttleInterval: number; @@ -91,6 +93,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { this.sender = emailProcessorConfig.getString('sender'); this.replyTo = emailProcessorConfig.getOptionalString('replyTo'); this.sesConfig = emailProcessorConfig.getOptionalConfig('sesConfig'); + this.sesOptions = this.getSesOptions(); this.concurrencyLimit = emailProcessorConfig.getOptionalNumber('concurrencyLimit') ?? 2; this.throttleInterval = emailProcessorConfig.has('throttleInterval') @@ -307,20 +310,23 @@ export class NotificationsEmailProcessor implements NotificationProcessor { return contentParts.join('\n\n'); } - private async getSesOptions() { + private getSesOptions(): Partial | undefined { if (!this.sesConfig) { return undefined; } - const ses: Record = {}; - const sourceArn = this.sesConfig.getOptionalString('sourceArn'); + const ses: Partial = {}; const fromArn = this.sesConfig.getOptionalString('fromArn'); + const sourceArn = this.sesConfig.getOptionalString('sourceArn'); const configurationSetName = this.sesConfig.getOptionalString( 'configurationSetName', ); - if (sourceArn) ses.SourceArn = sourceArn; - if (fromArn) ses.FromArn = fromArn; + if (fromArn) ses.FromEmailAddressIdentityArn = fromArn; if (configurationSetName) ses.ConfigurationSetName = configurationSetName; + if (sourceArn) + this.logger.warn( + 'sourceArn is not supported in SESv2 and will be ignored', + ); return Object.keys(ses).length > 0 ? ses : undefined; } @@ -332,7 +338,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { html: this.getHtmlContent(notification), text: this.getTextContent(notification), replyTo: this.replyTo, - ses: await this.getSesOptions(), + ses: this.sesOptions, }; await this.sendMails(mailOptions, emails); @@ -350,7 +356,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { html: await this.templateRenderer?.getHtml?.(notification), text: await this.templateRenderer?.getText?.(notification), replyTo: this.replyTo, - ses: await this.getSesOptions(), + ses: this.sesOptions, }; await this.sendMails(mailOptions, emails); diff --git a/plugins/notifications-backend-module-slack/CHANGELOG.md b/plugins/notifications-backend-module-slack/CHANGELOG.md index f00983f9e9..4d9c711297 100644 --- a/plugins/notifications-backend-module-slack/CHANGELOG.md +++ b/plugins/notifications-backend-module-slack/CHANGELOG.md @@ -1,5 +1,48 @@ # @backstage/plugin-notifications-backend-module-slack +## 0.3.0 + +### Minor Changes + +- f95a516: Enables optional routes to Slack channels for broadcast notifications based on origin and/or topics. + +### Patch Changes + +- b80857a: Slack notification handler throttling can now be configured with the `concurrencyLimit` and `throttleInterval` options. +- f8230e4: Updated dependency `@faker-js/faker` to `^10.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-notifications-node@0.2.22 + +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/plugin-notifications-node@0.2.22-next.1 + +## 0.2.2-next.0 + +### Patch Changes + +- 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 + ## 0.2.1 ### Patch Changes diff --git a/plugins/notifications-backend-module-slack/config.d.ts b/plugins/notifications-backend-module-slack/config.d.ts index 8cd450940e..17a4e25e0d 100644 --- a/plugins/notifications-backend-module-slack/config.d.ts +++ b/plugins/notifications-backend-module-slack/config.d.ts @@ -13,6 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { HumanDuration } from '@backstage/types'; + export interface Config { notifications?: { processors?: { @@ -26,8 +28,40 @@ export interface Config { * Broadcast notification receivers when receiver is set to config * These can be Slack User IDs, Slack User Email addresses, Slack Channel * Names, or Slack Channel IDs. Any valid identifier that chat.postMessage can accept. + * @deprecated Use broadcastRoutes instead for more granular control */ broadcastChannels?: string[]; + /** + * Optional username to display as the sender of the notification + */ + username?: string; + /** + * Routes for broadcast notifications based on origin and/or topic. + * Routes are evaluated in order, first match wins. + * Origin+topic matches take precedence over origin-only matches. + */ + broadcastRoutes?: Array<{ + /** + * The origin to match (e.g., 'plugin:catalog', 'external:my-service') + */ + origin?: string; + /** + * The topic to match (e.g., 'entity-updated', 'alerts') + */ + topic?: string; + /** + * The Slack channel(s) to send to. Can be channel IDs, channel names, or user IDs. + */ + channel: string | string[]; + }>; + /** + * Concurrency limit for Slack notifications per backend instance of the notifications plugin, defaults to 10. + */ + concurrencyLimit?: number; + /** + * Throttle duration between Slack notifications per backend instance of the notifications plugin, defaults to 1 minute. + */ + throttleInterval?: HumanDuration | string; }>; }; }; diff --git a/plugins/notifications-backend-module-slack/package.json b/plugins/notifications-backend-module-slack/package.json index 14ac470e37..0e35577006 100644 --- a/plugins/notifications-backend-module-slack/package.json +++ b/plugins/notifications-backend-module-slack/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend-module-slack", - "version": "0.2.1", + "version": "0.3.0", "description": "The slack backend module for the notifications plugin.", "backstage": { "role": "backend-plugin-module", @@ -54,7 +54,7 @@ "@backstage/cli": "workspace:^", "@backstage/plugin-catalog-node": "workspace:^", "@backstage/test-utils": "workspace:^", - "@faker-js/faker": "^8.4.1", + "@faker-js/faker": "^10.0.0", "msw": "^2.0.0" }, "configSchema": "config.d.ts" diff --git a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts index ad4bf01e9f..9490ff7dde 100644 --- a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts +++ b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts @@ -19,6 +19,22 @@ import { SlackNotificationProcessor } from './SlackNotificationProcessor'; import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; import { WebClient } from '@slack/web-api'; import { Entity } from '@backstage/catalog-model'; +import pThrottle from 'p-throttle'; +import { durationToMilliseconds } from '@backstage/types'; + +const throttleConfigs: Array<{ limit: number; interval: number }> = []; + +jest.mock('p-throttle', () => ({ + __esModule: true, + default: jest.fn((config: { limit: number; interval: number }) => { + throttleConfigs.push(config); + return Promise | any>(fn: T) => + (...args: Parameters) => + Promise.resolve(fn(...args)); + }), +})); + +const mockedPThrottle = pThrottle as jest.MockedFunction; jest.mock('@slack/web-api', () => { const mockSlack = { @@ -128,6 +144,8 @@ describe('SlackNotificationProcessor', () => { beforeEach(() => { jest.clearAllMocks(); + throttleConfigs.length = 0; + mockedPThrottle.mockClear(); }); it('should send a notification to a group', async () => { @@ -393,6 +411,462 @@ describe('SlackNotificationProcessor', () => { }); }); + describe('when broadcast routes are configured', () => { + it('should route by origin and topic (highest priority)', async () => { + const slack = new WebClient(); + const routesConfig = mockServices.rootConfig({ + data: { + app: { + baseUrl: 'https://example.org', + }, + notifications: { + processors: { + slack: [ + { + token: 'mock-token', + broadcastChannels: ['default-channel'], + broadcastRoutes: [ + { origin: 'plugin:catalog', channel: 'catalog-channel' }, + { topic: 'alerts', channel: 'alerts-channel' }, + { + origin: 'plugin:catalog', + topic: 'alerts', + channel: 'catalog-alerts-channel', + }, + ], + }, + ], + }, + }, + }, + }); + + const processor = SlackNotificationProcessor.fromConfig(routesConfig, { + auth, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + slack, + })[0]; + + await processor.postProcess( + { + origin: 'plugin:catalog', + id: '1234', + user: null, + created: new Date(), + payload: { + title: 'notification', + topic: 'alerts', + }, + }, + { + recipients: { type: 'broadcast' }, + payload: { title: 'notification', topic: 'alerts' }, + }, + ); + + expect(slack.chat.postMessage).toHaveBeenCalledTimes(1); + expect(slack.chat.postMessage).toHaveBeenCalledWith( + expect.objectContaining({ channel: 'catalog-alerts-channel' }), + ); + }); + + it('should route by origin only when no origin+topic match', async () => { + const slack = new WebClient(); + const routesConfig = mockServices.rootConfig({ + data: { + app: { + baseUrl: 'https://example.org', + }, + notifications: { + processors: { + slack: [ + { + token: 'mock-token', + broadcastRoutes: [ + { origin: 'plugin:catalog', channel: 'catalog-channel' }, + { topic: 'alerts', channel: 'alerts-channel' }, + ], + }, + ], + }, + }, + }, + }); + + const processor = SlackNotificationProcessor.fromConfig(routesConfig, { + auth, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + slack, + })[0]; + + await processor.postProcess( + { + origin: 'plugin:catalog', + id: '1234', + user: null, + created: new Date(), + payload: { + title: 'notification', + topic: 'updates', + }, + }, + { + recipients: { type: 'broadcast' }, + payload: { title: 'notification', topic: 'updates' }, + }, + ); + + expect(slack.chat.postMessage).toHaveBeenCalledTimes(1); + expect(slack.chat.postMessage).toHaveBeenCalledWith( + expect.objectContaining({ channel: 'catalog-channel' }), + ); + }); + + it('should route by topic only when no origin match', async () => { + const slack = new WebClient(); + const routesConfig = mockServices.rootConfig({ + data: { + app: { + baseUrl: 'https://example.org', + }, + notifications: { + processors: { + slack: [ + { + token: 'mock-token', + broadcastRoutes: [ + { origin: 'plugin:catalog', channel: 'catalog-channel' }, + { topic: 'alerts', channel: 'alerts-channel' }, + ], + }, + ], + }, + }, + }, + }); + + const processor = SlackNotificationProcessor.fromConfig(routesConfig, { + auth, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + slack, + })[0]; + + await processor.postProcess( + { + origin: 'plugin:unknown', + id: '1234', + user: null, + created: new Date(), + payload: { + title: 'notification', + topic: 'alerts', + }, + }, + { + recipients: { type: 'broadcast' }, + payload: { title: 'notification', topic: 'alerts' }, + }, + ); + + expect(slack.chat.postMessage).toHaveBeenCalledTimes(1); + expect(slack.chat.postMessage).toHaveBeenCalledWith( + expect.objectContaining({ channel: 'alerts-channel' }), + ); + }); + + it('should fall back to broadcastChannels when no route matches', async () => { + const slack = new WebClient(); + const routesConfig = mockServices.rootConfig({ + data: { + app: { + baseUrl: 'https://example.org', + }, + notifications: { + processors: { + slack: [ + { + token: 'mock-token', + broadcastChannels: ['default-channel'], + broadcastRoutes: [ + { origin: 'plugin:catalog', channel: 'catalog-channel' }, + ], + }, + ], + }, + }, + }, + }); + + const processor = SlackNotificationProcessor.fromConfig(routesConfig, { + auth, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + slack, + })[0]; + + await processor.postProcess( + { + origin: 'plugin:unknown', + id: '1234', + user: null, + created: new Date(), + payload: { + title: 'notification', + }, + }, + { + recipients: { type: 'broadcast' }, + payload: { title: 'notification' }, + }, + ); + + expect(slack.chat.postMessage).toHaveBeenCalledTimes(1); + expect(slack.chat.postMessage).toHaveBeenCalledWith( + expect.objectContaining({ channel: 'default-channel' }), + ); + }); + + it('should support multiple channels in a route', async () => { + const slack = new WebClient(); + const routesConfig = mockServices.rootConfig({ + data: { + app: { + baseUrl: 'https://example.org', + }, + notifications: { + processors: { + slack: [ + { + token: 'mock-token', + broadcastRoutes: [ + { + origin: 'plugin:catalog', + channel: ['channel1', 'channel2', 'channel3'], + }, + ], + }, + ], + }, + }, + }, + }); + + const processor = SlackNotificationProcessor.fromConfig(routesConfig, { + auth, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + slack, + })[0]; + + await processor.postProcess( + { + origin: 'plugin:catalog', + id: '1234', + user: null, + created: new Date(), + payload: { + title: 'notification', + }, + }, + { + recipients: { type: 'broadcast' }, + payload: { title: 'notification' }, + }, + ); + + expect(slack.chat.postMessage).toHaveBeenCalledTimes(3); + expect(slack.chat.postMessage).toHaveBeenCalledWith( + expect.objectContaining({ channel: 'channel1' }), + ); + expect(slack.chat.postMessage).toHaveBeenCalledWith( + expect.objectContaining({ channel: 'channel2' }), + ); + expect(slack.chat.postMessage).toHaveBeenCalledWith( + expect.objectContaining({ channel: 'channel3' }), + ); + }); + + it('should support single channel as string in a route', async () => { + const slack = new WebClient(); + const routesConfig = mockServices.rootConfig({ + data: { + app: { + baseUrl: 'https://example.org', + }, + notifications: { + processors: { + slack: [ + { + token: 'mock-token', + broadcastRoutes: [ + { topic: 'alerts', channel: 'single-alerts-channel' }, + ], + }, + ], + }, + }, + }, + }); + + const processor = SlackNotificationProcessor.fromConfig(routesConfig, { + auth, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + slack, + })[0]; + + await processor.postProcess( + { + origin: 'plugin:any', + id: '1234', + user: null, + created: new Date(), + payload: { + title: 'notification', + topic: 'alerts', + }, + }, + { + recipients: { type: 'broadcast' }, + payload: { title: 'notification', topic: 'alerts' }, + }, + ); + + expect(slack.chat.postMessage).toHaveBeenCalledTimes(1); + expect(slack.chat.postMessage).toHaveBeenCalledWith( + expect.objectContaining({ channel: 'single-alerts-channel' }), + ); + }); + + it('should not send when no route matches and no broadcastChannels configured', async () => { + const slack = new WebClient(); + const routesConfig = mockServices.rootConfig({ + data: { + app: { + baseUrl: 'https://example.org', + }, + notifications: { + processors: { + slack: [ + { + token: 'mock-token', + broadcastRoutes: [ + { origin: 'plugin:catalog', channel: 'catalog-channel' }, + ], + }, + ], + }, + }, + }, + }); + + const processor = SlackNotificationProcessor.fromConfig(routesConfig, { + auth, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + slack, + })[0]; + + await processor.postProcess( + { + origin: 'plugin:unknown', + id: '1234', + user: null, + created: new Date(), + payload: { + title: 'notification', + topic: 'unknown-topic', + }, + }, + { + recipients: { type: 'broadcast' }, + payload: { title: 'notification', topic: 'unknown-topic' }, + }, + ); + + expect(slack.chat.postMessage).not.toHaveBeenCalled(); + }); + + it('should match origin+topic route over origin-only route', async () => { + const slack = new WebClient(); + const routesConfig = mockServices.rootConfig({ + data: { + app: { + baseUrl: 'https://example.org', + }, + notifications: { + processors: { + slack: [ + { + token: 'mock-token', + broadcastRoutes: [ + // Origin-only route defined first + { origin: 'plugin:catalog', channel: 'general-catalog' }, + // More specific origin+topic route defined second + { + origin: 'plugin:catalog', + topic: 'entity-deleted', + channel: 'catalog-deletions', + }, + ], + }, + ], + }, + }, + }, + }); + + const processor = SlackNotificationProcessor.fromConfig(routesConfig, { + auth, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + slack, + })[0]; + + await processor.postProcess( + { + origin: 'plugin:catalog', + id: '1234', + user: null, + created: new Date(), + payload: { + title: 'notification', + topic: 'entity-deleted', + }, + }, + { + recipients: { type: 'broadcast' }, + payload: { title: 'notification', topic: 'entity-deleted' }, + }, + ); + + // Should use the origin+topic match, not the origin-only match + expect(slack.chat.postMessage).toHaveBeenCalledTimes(1); + expect(slack.chat.postMessage).toHaveBeenCalledWith( + expect.objectContaining({ channel: 'catalog-deletions' }), + ); + }); + }); + describe('when slack.com/bot-notify annotation is missing', () => { it('should not send notification to a group without annotation', async () => { const slack = new WebClient(); @@ -953,4 +1427,81 @@ describe('SlackNotificationProcessor', () => { ); }); }); + + describe('when throttling is not configured', () => { + it('should use default concurrency limit of 10 per minute', async () => { + const slack = new WebClient(); + + const processor = SlackNotificationProcessor.fromConfig(config, { + auth, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + slack, + })[0]; + + await processor.processOptions({ + recipients: { type: 'entity', entityRef: 'group:default/mock' }, + payload: { title: 'notification' }, + }); + + expect(slack.chat.postMessage).toHaveBeenCalled(); + expect(throttleConfigs).toEqual([ + { + limit: 10, + interval: durationToMilliseconds({ minutes: 1 }), + }, + ]); + }); + }); + + describe('when throttling is configured', () => { + it('should use custom concurrency limit and interval values', async () => { + const slack = new WebClient(); + const throttlingConfig = mockServices.rootConfig({ + data: { + app: { + baseUrl: 'https://example.org', + }, + notifications: { + processors: { + slack: [ + { + token: 'mock-token', + concurrencyLimit: 5, + throttleInterval: 'PT30S', + }, + ], + }, + }, + }, + }); + + const processor = SlackNotificationProcessor.fromConfig( + throttlingConfig, + { + auth, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + slack, + }, + )[0]; + + await processor.processOptions({ + recipients: { type: 'entity', entityRef: 'group:default/mock' }, + payload: { title: 'notification' }, + }); + + expect(slack.chat.postMessage).toHaveBeenCalled(); + expect(throttleConfigs).toEqual([ + { + limit: 5, + interval: durationToMilliseconds({ seconds: 30 }), + }, + ]); + }); + }); }); diff --git a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts index f9b00c9a58..e8f8d9eda4 100644 --- a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts +++ b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts @@ -21,7 +21,7 @@ import { parseEntityRef, UserEntity, } from '@backstage/catalog-model'; -import { Config } from '@backstage/config'; +import { Config, readDurationFromConfig } from '@backstage/config'; import { NotFoundError } from '@backstage/errors'; import { Notification } from '@backstage/plugin-notifications-common'; import { @@ -34,6 +34,7 @@ import { ChatPostMessageArguments, WebClient } from '@slack/web-api'; import DataLoader from 'dataloader'; import pThrottle from 'p-throttle'; import { ANNOTATION_SLACK_BOT_NOTIFY } from './constants'; +import { BroadcastRoute } from './types'; import { ExpiryMap, toChatPostMessageArgs } from './util'; import { CatalogService } from '@backstage/plugin-catalog-node'; @@ -48,8 +49,11 @@ export class SlackNotificationProcessor implements NotificationProcessor { private readonly messagesSent: Counter; private readonly messagesFailed: Counter; private readonly broadcastChannels?: string[]; + private readonly broadcastRoutes?: BroadcastRoute[]; private readonly entityLoader: DataLoader; private readonly username?: string; + private readonly concurrencyLimit: number; + private readonly throttleInterval: number; static fromConfig( config: Config, @@ -68,10 +72,23 @@ export class SlackNotificationProcessor implements NotificationProcessor { const slack = options.slack ?? new WebClient(token); const broadcastChannels = c.getOptionalStringArray('broadcastChannels'); const username = c.getOptionalString('username'); + const broadcastRoutesConfig = c.getOptionalConfigArray('broadcastRoutes'); + const broadcastRoutes = broadcastRoutesConfig?.map(route => + this.parseBroadcastRoute(route), + ); + const concurrencyLimit = c.getOptionalNumber('concurrencyLimit') ?? 10; + const throttleInterval = c.has('throttleInterval') + ? durationToMilliseconds( + readDurationFromConfig(c, { key: 'throttleInterval' }), + ) + : durationToMilliseconds({ minutes: 1 }); return new SlackNotificationProcessor({ slack, broadcastChannels, + broadcastRoutes, username, + concurrencyLimit, + throttleInterval, ...options, }); }); @@ -83,16 +100,32 @@ export class SlackNotificationProcessor implements NotificationProcessor { logger: LoggerService; catalog: CatalogService; broadcastChannels?: string[]; + broadcastRoutes?: BroadcastRoute[]; username?: string; + concurrencyLimit?: number; + throttleInterval?: number; }) { - const { auth, catalog, logger, slack, broadcastChannels, username } = - options; + const { + auth, + catalog, + logger, + slack, + broadcastChannels, + broadcastRoutes, + username, + concurrencyLimit, + throttleInterval, + } = options; this.logger = logger; this.catalog = catalog; this.auth = auth; this.slack = slack; this.broadcastChannels = broadcastChannels; + this.broadcastRoutes = broadcastRoutes; this.username = username; + this.concurrencyLimit = concurrencyLimit ?? 10; + this.throttleInterval = + throttleInterval ?? durationToMilliseconds({ minutes: 1 }); this.entityLoader = new DataLoader( async entityRefs => { @@ -134,8 +167,8 @@ export class SlackNotificationProcessor implements NotificationProcessor { ); const throttle = pThrottle({ - limit: 10, - interval: durationToMilliseconds({ minutes: 1 }), + limit: this.concurrencyLimit, + interval: this.throttleInterval, }); const throttled = throttle((opts: ChatPostMessageArguments) => this.sendNotification(opts), @@ -235,7 +268,8 @@ export class SlackNotificationProcessor implements NotificationProcessor { // Handle broadcast case if (notification.user === null) { - destinations.push(...(this.broadcastChannels ?? [])); + const routedChannels = this.getBroadcastDestinations(notification); + destinations.push(...routedChannels); } else if (options.recipients.type === 'entity') { // Handle user-specific notification const entityRefs = [options.recipients.entityRef].flat(); @@ -384,4 +418,88 @@ export class SlackNotificationProcessor implements NotificationProcessor { throw new Error(`Failed to send notification: ${response.error}`); } } + + private static parseBroadcastRoute(route: Config): BroadcastRoute { + const channelValue = route.getOptional('channel'); + let channels: string[]; + + if (typeof channelValue === 'string') { + channels = [channelValue]; + } else if (Array.isArray(channelValue)) { + channels = channelValue as string[]; + } else { + throw new Error( + 'broadcastRoutes entry must have a channel property (string or string[])', + ); + } + + return { + origin: route.getOptionalString('origin'), + topic: route.getOptionalString('topic'), + channels, + }; + } + + /** + * Gets the destination channels for a broadcast notification based on + * configured routes. Routes are matched by origin and/or topic. + * + * Matching precedence: + * 1. Routes with both origin AND topic matching (most specific) + * 2. Routes with only origin matching + * 3. Routes with only topic matching + * 4. Default broadcastChannels (least specific fallback) + * + * The first matching route wins within each precedence level. + */ + private getBroadcastDestinations(notification: Notification): string[] { + const { origin } = notification; + const { topic } = notification.payload; + + if (!this.broadcastRoutes || this.broadcastRoutes.length === 0) { + // Fall back to legacy broadcastChannels config + return this.broadcastChannels ?? []; + } + + // Find most specific match + // Priority 1: origin AND topic match + const originAndTopicMatch = this.broadcastRoutes.find( + route => + route.origin !== undefined && + route.topic !== undefined && + route.origin === origin && + route.topic === topic, + ); + + if (originAndTopicMatch) { + return originAndTopicMatch.channels; + } + + // Priority 2: origin-only match (no topic specified in route) + const originOnlyMatch = this.broadcastRoutes.find( + route => + route.origin !== undefined && + route.topic === undefined && + route.origin === origin, + ); + + if (originOnlyMatch) { + return originOnlyMatch.channels; + } + + // Priority 3: topic-only match (no origin specified in route) + const topicOnlyMatch = this.broadcastRoutes.find( + route => + route.topic !== undefined && + route.origin === undefined && + route.topic === topic, + ); + + if (topicOnlyMatch) { + return topicOnlyMatch.channels; + } + + // No match found, fall back to legacy broadcastChannels + return this.broadcastChannels ?? []; + } } diff --git a/plugins/notifications-backend-module-slack/src/lib/types.ts b/plugins/notifications-backend-module-slack/src/lib/types.ts index bd87e9656c..316df03462 100644 --- a/plugins/notifications-backend-module-slack/src/lib/types.ts +++ b/plugins/notifications-backend-module-slack/src/lib/types.ts @@ -18,3 +18,16 @@ export interface SlackNotificationOptions { url: string; payload: string; } + +/** + * Configuration for routing broadcast notifications to specific Slack channels + * based on origin and/or topic. + */ +export type BroadcastRoute = { + /** The origin to match (e.g., 'plugin:catalog', 'external:my-service') */ + origin?: string; + /** The topic to match (e.g., 'entity-updated', 'alerts') */ + topic?: string; + /** The Slack channel(s) to send to */ + channels: string[]; +}; diff --git a/plugins/notifications-backend/CHANGELOG.md b/plugins/notifications-backend/CHANGELOG.md index 75e9464dac..91da5468ca 100644 --- a/plugins/notifications-backend/CHANGELOG.md +++ b/plugins/notifications-backend/CHANGELOG.md @@ -1,5 +1,47 @@ # @backstage/plugin-notifications-backend +## 0.6.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-signals-node@0.1.27 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-notifications-node@0.2.22 + +## 0.6.1-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-signals-node@0.1.27-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/plugin-notifications-node@0.2.22-next.1 + +## 0.6.1-next.0 + +### Patch Changes + +- 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 + ## 0.6.0 ### Minor Changes diff --git a/plugins/notifications-backend/package.json b/plugins/notifications-backend/package.json index 2d8b3e7aed..7262b65d38 100644 --- a/plugins/notifications-backend/package.json +++ b/plugins/notifications-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend", - "version": "0.6.0", + "version": "0.6.1", "backstage": { "role": "backend-plugin", "pluginId": "notifications", @@ -48,7 +48,7 @@ "@backstage/plugin-notifications-node": "workspace:^", "@backstage/plugin-signals-node": "workspace:^", "@backstage/types": "workspace:^", - "express": "^4.17.1", + "express": "^4.22.0", "express-promise-router": "^4.1.0", "knex": "^3.0.0", "p-throttle": "^4.1.1", diff --git a/plugins/notifications-node/CHANGELOG.md b/plugins/notifications-node/CHANGELOG.md index aa32fe4c10..98d1b3c84c 100644 --- a/plugins/notifications-node/CHANGELOG.md +++ b/plugins/notifications-node/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-notifications-node +## 0.2.22 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-signals-node@0.1.27 + - @backstage/backend-plugin-api@1.6.0 + +## 0.2.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-signals-node@0.1.27-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/plugin-notifications-common@0.2.0 + +## 0.2.22-next.0 + +### Patch Changes + +- 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 + ## 0.2.21 ### Patch Changes diff --git a/plugins/notifications-node/package.json b/plugins/notifications-node/package.json index 7d9381e20f..8007852dd5 100644 --- a/plugins/notifications-node/package.json +++ b/plugins/notifications-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-node", - "version": "0.2.21", + "version": "0.2.22", "description": "Node.js library for the notifications plugin", "backstage": { "role": "node-library", diff --git a/plugins/notifications/CHANGELOG.md b/plugins/notifications/CHANGELOG.md index 2b090361f9..50bd9b35a3 100644 --- a/plugins/notifications/CHANGELOG.md +++ b/plugins/notifications/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-notifications +## 0.5.12 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- 53347cc: Move long notification descriptions behind `Show more/less` button. + + This improves readability of the notifications list by preventing long descriptions from taking up too much space + or rendering very small scrollable areas. + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/plugin-signals-react@0.0.18 + +## 0.5.12-next.0 + +### Patch Changes + +- 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 + ## 0.5.11 ### Patch Changes diff --git a/plugins/notifications/package.json b/plugins/notifications/package.json index dbafa426f6..1a6b544da3 100644 --- a/plugins/notifications/package.json +++ b/plugins/notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications", - "version": "0.5.11", + "version": "0.5.12", "backstage": { "role": "frontend-plugin", "pluginId": "notifications", @@ -51,7 +51,6 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", diff --git a/plugins/notifications/report-alpha.api.md b/plugins/notifications/report-alpha.api.md index 713d4c3998..98d036365e 100644 --- a/plugins/notifications/report-alpha.api.md +++ b/plugins/notifications/report-alpha.api.md @@ -11,7 +11,8 @@ import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; -import { RouteRef } from '@backstage/frontend-plugin-api'; +import { RouteRef } from '@backstage/core-plugin-api'; +import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) const _default: OverridableFrontendPlugin< @@ -48,7 +49,7 @@ const _default: OverridableFrontendPlugin< | ExtensionDataRef | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; @@ -59,7 +60,7 @@ const _default: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; loader: () => Promise; - routeRef?: RouteRef; + routeRef?: RouteRef_2; }; }>; } diff --git a/plugins/notifications/report.api.md b/plugins/notifications/report.api.md index a9aeb64cda..f42814f983 100644 --- a/plugins/notifications/report.api.md +++ b/plugins/notifications/report.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 { ApiRef } from '@backstage/core-plugin-api'; +import { ApiRef } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { DiscoveryApi } from '@backstage/core-plugin-api'; import { FetchApi } from '@backstage/core-plugin-api'; diff --git a/plugins/notifications/src/alpha.tsx b/plugins/notifications/src/alpha.tsx index ca4be58209..32b9ae429a 100644 --- a/plugins/notifications/src/alpha.tsx +++ b/plugins/notifications/src/alpha.tsx @@ -22,21 +22,16 @@ import { fetchApiRef, } from '@backstage/frontend-plugin-api'; import { rootRouteRef } from './routes'; -import { - compatWrapper, - convertLegacyRouteRef, - convertLegacyRouteRefs, -} from '@backstage/core-compat-api'; import { NotificationsClient, notificationsApiRef } from './api'; const page = PageBlueprint.make({ params: { path: '/notifications', - routeRef: convertLegacyRouteRef(rootRouteRef), + routeRef: rootRouteRef, loader: () => - import('./components/NotificationsPage').then(m => - compatWrapper(), - ), + import('./components/NotificationsPage').then(m => ( + + )), }, }); @@ -54,9 +49,9 @@ const api = ApiBlueprint.make({ export default createFrontendPlugin({ pluginId: 'notifications', info: { packageJson: () => import('../package.json') }, - routes: convertLegacyRouteRefs({ + routes: { root: rootRouteRef, - }), + }, // TODO(Rugvip): Nav item (i.e. NotificationsSidebarItem) currently needs to be installed manually extensions: [page, api], }); diff --git a/plugins/notifications/src/components/NotificationsFilters/NotificationsFilters.tsx b/plugins/notifications/src/components/NotificationsFilters/NotificationsFilters.tsx index 7e3f519b0c..e8fa422fa1 100644 --- a/plugins/notifications/src/components/NotificationsFilters/NotificationsFilters.tsx +++ b/plugins/notifications/src/components/NotificationsFilters/NotificationsFilters.tsx @@ -14,14 +14,11 @@ * limitations under the License. */ import { ChangeEvent } from 'react'; - -import Divider from '@material-ui/core/Divider'; import FormControl from '@material-ui/core/FormControl'; import Grid from '@material-ui/core/Grid'; import InputLabel from '@material-ui/core/InputLabel'; import MenuItem from '@material-ui/core/MenuItem'; import Select from '@material-ui/core/Select'; -import Typography from '@material-ui/core/Typography'; import { GetNotificationsOptions } from '../../api'; import { NotificationSeverity } from '@backstage/plugin-notifications-common'; @@ -200,11 +197,6 @@ export const NotificationsFilters = ({ return ( <> - - Filters - - - View diff --git a/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx b/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx index 739e9a1535..69242ac60e 100644 --- a/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx +++ b/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { useState, useMemo, useEffect } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import throttle from 'lodash/throttle'; import { Content, @@ -156,13 +156,17 @@ export const NotificationsPage = (props?: NotificationsPageProps) => { const isUnread = !!value?.[1]?.unread; const allTopics = value?.[2]?.topics; - let tableTitle = `All notifications (${totalCount})`; + let tableTitle = `All notifications `; if (saved) { - tableTitle = `Saved notifications (${totalCount})`; + tableTitle = `Saved notifications`; } else if (unreadOnly === true) { - tableTitle = `Unread notifications (${totalCount})`; + tableTitle = `Unread notifications`; } else if (unreadOnly === false) { - tableTitle = `Read notifications (${totalCount})`; + tableTitle = `Read notifications`; + } + + if (totalCount) { + tableTitle += ` (${totalCount})`; } return ( diff --git a/plugins/notifications/src/components/NotificationsTable/NotificationDescription.tsx b/plugins/notifications/src/components/NotificationsTable/NotificationDescription.tsx new file mode 100644 index 0000000000..762900c2b9 --- /dev/null +++ b/plugins/notifications/src/components/NotificationsTable/NotificationDescription.tsx @@ -0,0 +1,61 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import Typography from '@material-ui/core/Typography'; +import Button from '@material-ui/core/Button'; +import { useState } from 'react'; + +const MAX_LENGTH = 100; + +export const NotificationDescription = (props: { description: string }) => { + const { description } = props; + const [shown, setShown] = useState(false); + const isLong = description.length > MAX_LENGTH; + + if (!isLong) { + return {description}; + } + + if (shown) { + return ( + + {description}{' '} + + + ); + } + return ( + + {description.substring(0, MAX_LENGTH)}...{' '} + + + ); +}; diff --git a/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx b/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx index 8ec237e62f..952c18f9f9 100644 --- a/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx +++ b/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { useState, useCallback, useMemo, useEffect } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import throttle from 'lodash/throttle'; // @ts-ignore import RelativeTime from 'react-relative-time'; @@ -37,14 +37,11 @@ import { notificationsApiRef } from '../../api'; import { SelectAll } from './SelectAll'; import { BulkActions } from './BulkActions'; import { NotificationIcon } from './NotificationIcon'; +import { NotificationDescription } from './NotificationDescription'; const ThrottleDelayMs = 1000; const useStyles = makeStyles(theme => ({ - description: { - maxHeight: '5rem', - overflow: 'auto', - }, severityItem: { alignContent: 'center', }, @@ -53,8 +50,10 @@ const useStyles = makeStyles(theme => ({ verticalAlign: 'text-bottom', }, notificationInfoRow: { - marginLeft: theme.spacing(0.5), marginRight: theme.spacing(0.5), + '&:not(:first-child)': { + marginLeft: theme.spacing(0.5), + }, }, })); @@ -240,9 +239,9 @@ export const NotificationsTable = ({ )} {notification.payload.description ? ( - - {notification.payload.description} - + ) : null} @@ -318,7 +317,6 @@ export const NotificationsTable = ({ onMarkAllRead, onNotificationsSelectChange, classes.severityItem, - classes.description, classes.broadcastIcon, classes.notificationInfoRow, markAsReadOnLinkOpen, diff --git a/plugins/notifications/src/components/NotificationsTable/SelectAll.tsx b/plugins/notifications/src/components/NotificationsTable/SelectAll.tsx index 9bcc497132..f6d6ff48c9 100644 --- a/plugins/notifications/src/components/NotificationsTable/SelectAll.tsx +++ b/plugins/notifications/src/components/NotificationsTable/SelectAll.tsx @@ -16,6 +16,7 @@ import Checkbox from '@material-ui/core/Checkbox'; import FormControlLabel from '@material-ui/core/FormControlLabel'; import { makeStyles } from '@material-ui/core/styles'; +import Tooltip from '@material-ui/core/Tooltip'; const useStyles = makeStyles({ label: { @@ -23,6 +24,7 @@ const useStyles = makeStyles({ maxWidth: '2rem', '& span': { paddingRight: '0px', + marginRight: '2px', }, }, }); @@ -43,13 +45,15 @@ export const SelectAll = ({ label={count > 0 ? `(${count})` : undefined} className={classes.label} control={ - 0} - indeterminate={count > 0 && totalCount !== count} - onChange={onSelectAll} - /> + + 0} + indeterminate={count > 0 && totalCount !== count} + onChange={onSelectAll} + /> + } /> ); diff --git a/plugins/org-react/CHANGELOG.md b/plugins/org-react/CHANGELOG.md index 276defee37..ffe7af52f1 100644 --- a/plugins/org-react/CHANGELOG.md +++ b/plugins/org-react/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-org-react +## 0.1.45 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + +## 0.1.45-next.0 + +### Patch Changes + +- 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 + ## 0.1.44 ### Patch Changes diff --git a/plugins/org-react/package.json b/plugins/org-react/package.json index 7ee1e57b20..99403d689d 100644 --- a/plugins/org-react/package.json +++ b/plugins/org-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org-react", - "version": "0.1.44", + "version": "0.1.45", "backstage": { "role": "web-library", "pluginId": "org", diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index df05c5e60f..90292a0f78 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,43 @@ # @backstage/plugin-org +## 0.6.47 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + +## 0.6.47-next.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-model@1.7.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/plugin-catalog-common@1.1.7 + +## 0.6.47-next.0 + +### Patch Changes + +- 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 + ## 0.6.46 ### Patch Changes diff --git a/plugins/org/package.json b/plugins/org/package.json index d2f44a3c5c..a48dad9f25 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org", - "version": "0.6.46", + "version": "0.6.47", "description": "A Backstage plugin that helps you create entity pages for your organization", "backstage": { "role": "frontend-plugin", @@ -52,7 +52,6 @@ }, "dependencies": { "@backstage/catalog-model": "workspace:^", - "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", diff --git a/plugins/org/report-alpha.api.md b/plugins/org/report-alpha.api.md index 3e113868a6..93e81f0851 100644 --- a/plugins/org/report-alpha.api.md +++ b/plugins/org/report-alpha.api.md @@ -7,8 +7,7 @@ import { Entity } from '@backstage/catalog-model'; import { EntityCardType } from '@backstage/plugin-catalog-react/alpha'; import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; -import { ExtensionInput } from '@backstage/frontend-plugin-api'; -import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; +import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; @@ -18,7 +17,7 @@ import { TranslationRef } from '@backstage/frontend-plugin-api'; const _default: OverridableFrontendPlugin< {}, { - catalogIndex: ExternalRouteRef; + catalogIndex: ExternalRouteRef; }, { 'entity-card:org/group-profile': OverridableExtensionDefinition<{ @@ -66,14 +65,12 @@ const _default: OverridableFrontendPlugin< config: { initialRelationAggregation: 'direct' | 'aggregated' | undefined; showAggregateMembersToggle: boolean | undefined; - } & { filter: EntityPredicate | undefined; type: 'content' | 'summary' | 'info' | undefined; }; configInput: { showAggregateMembersToggle?: boolean | undefined; initialRelationAggregation?: 'direct' | 'aggregated' | undefined; - } & { filter?: EntityPredicate | undefined; type?: 'content' | 'summary' | 'info' | undefined; }; @@ -100,15 +97,7 @@ const _default: OverridableFrontendPlugin< optional: true; } >; - inputs: { - [x: string]: ExtensionInput< - ExtensionDataRef, - { - singleton: boolean; - optional: boolean; - } - >; - }; + inputs: {}; kind: 'entity-card'; name: 'members-list'; params: { @@ -121,14 +110,12 @@ const _default: OverridableFrontendPlugin< config: { initialRelationAggregation: 'direct' | 'aggregated' | undefined; showAggregateMembersToggle: boolean | undefined; - } & { filter: EntityPredicate | undefined; type: 'content' | 'summary' | 'info' | undefined; }; configInput: { showAggregateMembersToggle?: boolean | undefined; initialRelationAggregation?: 'direct' | 'aggregated' | undefined; - } & { filter?: EntityPredicate | undefined; type?: 'content' | 'summary' | 'info' | undefined; }; @@ -155,15 +142,7 @@ const _default: OverridableFrontendPlugin< optional: true; } >; - inputs: { - [x: string]: ExtensionInput< - ExtensionDataRef, - { - singleton: boolean; - optional: boolean; - } - >; - }; + inputs: {}; kind: 'entity-card'; name: 'ownership'; params: { @@ -176,14 +155,12 @@ const _default: OverridableFrontendPlugin< config: { maxRelations: number | undefined; hideIcons: boolean; - } & { filter: EntityPredicate | undefined; type: 'content' | 'summary' | 'info' | undefined; }; configInput: { hideIcons?: boolean | undefined; maxRelations?: number | undefined; - } & { filter?: EntityPredicate | undefined; type?: 'content' | 'summary' | 'info' | undefined; }; @@ -210,15 +187,7 @@ const _default: OverridableFrontendPlugin< optional: true; } >; - inputs: { - [x: string]: ExtensionInput< - ExtensionDataRef, - { - singleton: boolean; - optional: boolean; - } - >; - }; + inputs: {}; kind: 'entity-card'; name: 'user-profile'; params: { diff --git a/plugins/org/src/alpha.tsx b/plugins/org/src/alpha.tsx index ddb8094688..57924a0269 100644 --- a/plugins/org/src/alpha.tsx +++ b/plugins/org/src/alpha.tsx @@ -14,10 +14,6 @@ * limitations under the License. */ -import { - compatWrapper, - convertLegacyRouteRefs, -} from '@backstage/core-compat-api'; import { createFrontendPlugin } from '@backstage/frontend-plugin-api'; import { catalogIndexRouteRef } from './routes'; import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha'; @@ -29,8 +25,8 @@ const EntityGroupProfileCard = EntityCardBlueprint.make({ type: 'info', filter: { kind: 'group' }, loader: async () => - import('./components/Cards/Group/GroupProfile/GroupProfileCard').then(m => - compatWrapper(), + import('./components/Cards/Group/GroupProfile/GroupProfileCard').then( + m => , ), }, }); @@ -49,12 +45,12 @@ const EntityMembersListCard = EntityCardBlueprint.makeWithOverrides({ return originalFactory({ filter: { kind: 'group' }, loader: async () => - import('./components/Cards/Group/MembersList/MembersListCard').then(m => - compatWrapper( + import('./components/Cards/Group/MembersList/MembersListCard').then( + m => ( , + /> ), ), }); @@ -75,19 +71,16 @@ const EntityOwnershipCard = EntityCardBlueprint.makeWithOverrides({ return originalFactory({ filter: { kind: { $in: ['group', 'user'] } }, loader: async () => - import('./components/Cards/OwnershipCard/OwnershipCard').then(m => - compatWrapper( - , - ), - ), + import('./components/Cards/OwnershipCard/OwnershipCard').then(m => ( + + )), }); }, }); @@ -107,13 +100,12 @@ const EntityUserProfileCard = EntityCardBlueprint.makeWithOverrides({ filter: { kind: 'user' }, loader: async () => import('./components/Cards/User/UserProfileCard/UserProfileCard').then( - m => - compatWrapper( - , - ), + m => ( + + ), ), }); }, @@ -129,9 +121,9 @@ export default createFrontendPlugin({ EntityOwnershipCard, EntityUserProfileCard, ], - externalRoutes: convertLegacyRouteRefs({ + externalRoutes: { catalogIndex: catalogIndexRouteRef, - }), + }, }); export { orgTranslationRef } from './translation'; diff --git a/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md b/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md index 58e281caf0..a9709fd88b 100644 --- a/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md +++ b/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/plugin-permission-backend-module-allow-all-policy +## 0.2.15 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + +## 0.2.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/plugin-permission-common@0.9.3 + +## 0.2.15-next.0 + +### Patch Changes + +- 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 + ## 0.2.14 ### Patch Changes diff --git a/plugins/permission-backend-module-policy-allow-all/package.json b/plugins/permission-backend-module-policy-allow-all/package.json index bf988ded4e..5cddb60c98 100644 --- a/plugins/permission-backend-module-policy-allow-all/package.json +++ b/plugins/permission-backend-module-policy-allow-all/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend-module-allow-all-policy", - "version": "0.2.14", + "version": "0.2.15", "description": "Allow all policy backend module for the permission plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/permission-backend/CHANGELOG.md b/plugins/permission-backend/CHANGELOG.md index 12d63d5c4b..196de3238b 100644 --- a/plugins/permission-backend/CHANGELOG.md +++ b/plugins/permission-backend/CHANGELOG.md @@ -1,5 +1,40 @@ # @backstage/plugin-permission-backend +## 0.7.7 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + +## 0.7.7-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.3 + +## 0.7.7-next.0 + +### Patch Changes + +- 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 + ## 0.7.6 ### Patch Changes diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index c3eb4a1146..f3c840b693 100644 --- a/plugins/permission-backend/package.json +++ b/plugins/permission-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend", - "version": "0.7.6", + "version": "0.7.7", "backstage": { "role": "backend-plugin", "pluginId": "permission", @@ -58,7 +58,7 @@ "@backstage/plugin-permission-common": "workspace:^", "@backstage/plugin-permission-node": "workspace:^", "dataloader": "^2.0.0", - "express": "^4.17.1", + "express": "^4.22.0", "express-promise-router": "^4.1.0", "lodash": "^4.17.21", "yn": "^4.0.0", diff --git a/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts b/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts index ece99f08c7..822d10b858 100644 --- a/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts +++ b/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts @@ -148,9 +148,7 @@ describe('PermissionIntegrationClient', () => { ], ); - expect(response).toEqual( - expect.objectContaining([{ id: '123', result: AuthorizeResult.ALLOW }]), - ); + expect(response).toEqual([{ id: '123', result: AuthorizeResult.ALLOW }]); }); it('should not include authorization headers if no token is supplied', async () => { diff --git a/plugins/permission-node/CHANGELOG.md b/plugins/permission-node/CHANGELOG.md index 6ac95b97ba..6c9fe8a91b 100644 --- a/plugins/permission-node/CHANGELOG.md +++ b/plugins/permission-node/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/plugin-permission-node +## 0.10.7 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-plugin-api@1.6.0 + +## 0.10.7-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.3 + +## 0.10.7-next.0 + +### Patch Changes + +- 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 + ## 0.10.6 ### Patch Changes diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json index e4c307f0ca..1ae3935b4c 100644 --- a/plugins/permission-node/package.json +++ b/plugins/permission-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-node", - "version": "0.10.6", + "version": "0.10.7", "description": "Common permission and authorization utilities for backend plugins", "backstage": { "role": "node-library", @@ -62,7 +62,7 @@ "@backstage/plugin-auth-node": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", "@types/express": "^4.17.6", - "express": "^4.17.1", + "express": "^4.22.0", "express-promise-router": "^4.1.0", "zod": "^3.22.4", "zod-to-json-schema": "^3.20.4" diff --git a/plugins/permission-react/CHANGELOG.md b/plugins/permission-react/CHANGELOG.md index 8cdd944d36..9a377d5dc8 100644 --- a/plugins/permission-react/CHANGELOG.md +++ b/plugins/permission-react/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-permission-react +## 0.4.39 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1 + +## 0.4.39-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/config@1.3.6 + - @backstage/plugin-permission-common@0.9.3 + ## 0.4.38 ### Patch Changes diff --git a/plugins/permission-react/package.json b/plugins/permission-react/package.json index 521347e4ab..17b4d65b51 100644 --- a/plugins/permission-react/package.json +++ b/plugins/permission-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-react", - "version": "0.4.38", + "version": "0.4.39", "backstage": { "role": "web-library", "pluginId": "permission", diff --git a/plugins/proxy-backend/CHANGELOG.md b/plugins/proxy-backend/CHANGELOG.md index 0830b908c4..7541c8faf1 100644 --- a/plugins/proxy-backend/CHANGELOG.md +++ b/plugins/proxy-backend/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-proxy-backend +## 0.6.9 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-proxy-node@0.1.11 + +## 0.6.9-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/types@1.2.2 + - @backstage/plugin-proxy-node@0.1.11-next.1 + +## 0.6.9-next.0 + +### Patch Changes + +- 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 + ## 0.6.8 ### Patch Changes diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index e011804893..c75e9a42e7 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-proxy-backend", - "version": "0.6.8", + "version": "0.6.9", "description": "A Backstage backend plugin that helps you set up proxy endpoints in the backend", "backstage": { "role": "backend-plugin", @@ -64,7 +64,7 @@ "@backstage/errors": "workspace:^", "@types/express": "^4.17.6", "@types/http-proxy-middleware": "^1.0.0", - "express": "^4.17.1", + "express": "^4.22.0", "msw": "^2.0.0" }, "configSchema": "config.d.ts" diff --git a/plugins/proxy-node/CHANGELOG.md b/plugins/proxy-node/CHANGELOG.md index 2622a272f3..e2bf6a15cf 100644 --- a/plugins/proxy-node/CHANGELOG.md +++ b/plugins/proxy-node/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-proxy-node +## 0.1.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + +## 0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + +## 0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.5.1-next.0 + ## 0.1.10 ### Patch Changes diff --git a/plugins/proxy-node/package.json b/plugins/proxy-node/package.json index eb0561173a..9134a6c2b5 100644 --- a/plugins/proxy-node/package.json +++ b/plugins/proxy-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-proxy-node", - "version": "0.1.10", + "version": "0.1.11", "description": "The plugin-proxy-node module for @backstage/plugin-proxy-backend", "backstage": { "role": "node-library", diff --git a/plugins/scaffolder-backend-module-azure/CHANGELOG.md b/plugins/scaffolder-backend-module-azure/CHANGELOG.md index f34ce2508e..6061d57a41 100644 --- a/plugins/scaffolder-backend-module-azure/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-azure/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-scaffolder-backend-module-azure +## 0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 0.2.16-next.0 + +### Patch Changes + +- 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 + ## 0.2.15 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-azure/package.json b/plugins/scaffolder-backend-module-azure/package.json index 8cf97f115c..d76d5c41d8 100644 --- a/plugins/scaffolder-backend-module-azure/package.json +++ b/plugins/scaffolder-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-azure", - "version": "0.2.15", + "version": "0.2.16", "description": "The azure module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md index e1544349dd..d18fbaf5d4 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,62 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket-cloud +## 0.3.0 + +### Minor Changes + +- 37fba1d: Added support for Bitbucket Cloud OAuth. This introduces an alternative authentication method using a workspace OAuth consumer, alongside App Passwords (deprecated) and API tokens. OAuth does not require a bot or service account and avoids token expiry issues. + + **BREAKING CHANGES** + + - **@backstage/integration** (`src/bitbucketCloud/core.ts`) + + - `getBitbucketCloudRequestOptions` now returns a `Promise` and **must** be awaited. + + - **@backstage/plugin-scaffolder-backend-module-bitbucket-cloud** (`src/actions/helpers.ts`) + - `getBitbucketClient` now returns a `Promise` and **must** be awaited. + - `getAuthorizationHeader` now returns a `Promise` and **must** be awaited. + + **OAuth usage example** + + ```yaml + integrations: + bitbucketCloud: + - clientId: client-id + clientSecret: client-secret + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.5 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-bitbucket-cloud-common@0.3.5-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 0.2.16-next.0 + +### Patch Changes + +- 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 + ## 0.2.15 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json index 98ab7b989d..512e5a7c55 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud", - "version": "0.2.15", + "version": "0.3.0", "description": "The Bitbucket Cloud module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts index 722bf01303..a278410639 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts @@ -25,6 +25,7 @@ import { import { Config } from '@backstage/config'; import { getAuthorizationHeader } from './helpers'; +import { getBitbucketCloudOAuthToken } from '@backstage/integration'; import { examples } from './bitbucketCloud.examples'; const createRepository = async (opts: { @@ -216,7 +217,7 @@ export function createPublishBitbucketCloudAction(options: { ); } - const authorization = getAuthorizationHeader( + const authorization = await getAuthorizationHeader( ctx.input.token ? { token: ctx.input.token } : integrationConfig.config, ); @@ -249,6 +250,18 @@ export function createPublishBitbucketCloudAction(options: { username: 'x-token-auth', password: ctx.input.token, }; + } else if ( + integrationConfig.config.clientId && + integrationConfig.config.clientSecret + ) { + const token = await getBitbucketCloudOAuthToken( + integrationConfig.config.clientId, + integrationConfig.config.clientSecret, + ); + auth = { + username: 'x-token-auth', + password: token, + }; } else { if ( !integrationConfig.config.username || diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.test.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.test.ts index 55ee430091..2ac54894d5 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.test.ts @@ -22,11 +22,11 @@ jest.mock('bitbucket', () => ({ })); describe('bitbucketCloud:branchRestriction:create', () => { - it('getBitbucketClient should return the correct headers with username and password', () => { + it('getBitbucketClient should return the correct headers with username and password', async () => { expect.assertions(1); const username = 'username'; const password = 'password'; - getBitbucketClient({ username: username, appPassword: password }); + await getBitbucketClient({ username: username, appPassword: password }); expect(Bitbucket).toHaveBeenCalledWith({ auth: { username: username, @@ -35,12 +35,14 @@ describe('bitbucketCloud:branchRestriction:create', () => { }); }); - it('getBitbucketClient should throw if only one of username or password is provided', () => { + it('getBitbucketClient should throw if only one of username or password is provided', async () => { expect.assertions(2); const username = 'username'; const password = 'password'; - expect(() => getBitbucketClient({ username })).toThrow(Error); - expect(() => getBitbucketClient({ appPassword: password })).toThrow(Error); + await expect(getBitbucketClient({ username })).rejects.toThrow(Error); + await expect(getBitbucketClient({ appPassword: password })).rejects.toThrow( + Error, + ); }); }); diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.ts index 233818e00a..a12f63f980 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.ts @@ -37,6 +37,8 @@ const createBitbucketCloudBranchRestriction = async (opts: { token?: string; username?: string; appPassword?: string; + clientId?: string; + clientSecret?: string; }; }) => { const { @@ -52,7 +54,7 @@ const createBitbucketCloudBranchRestriction = async (opts: { authorization, } = opts; - const bitbucket = getBitbucketClient(authorization); + const bitbucket = await getBitbucketClient(authorization); return await bitbucket.branchrestrictions.create({ _body: { groups: groups, diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.ts index 4bf9226db7..372f45c928 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.ts @@ -68,7 +68,7 @@ export const createBitbucketPipelinesRunAction = (options: { const host = 'bitbucket.org'; const integrationConfig = integrations.bitbucketCloud.byHost(host); - const authorization = getAuthorizationHeader( + const authorization = await getAuthorizationHeader( token ? { token } : integrationConfig!.config, ); let response: Response; diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPullRequest.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPullRequest.ts index 9969f13eb2..40dd09d386 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPullRequest.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPullRequest.ts @@ -28,6 +28,7 @@ import { import { Config } from '@backstage/config'; import fs from 'fs-extra'; import { getAuthorizationHeader } from './helpers'; +import { getBitbucketCloudOAuthToken } from '@backstage/integration'; import { examples } from './bitbucketCloudPullRequest.examples'; const createPullRequest = async (opts: { @@ -317,7 +318,7 @@ export function createPublishBitbucketCloudPullRequestAction(options: { ); } - const authorization = getAuthorizationHeader( + const authorization = await getAuthorizationHeader( ctx.input.token ? { token: ctx.input.token } : integrationConfig.config, ); @@ -365,6 +366,18 @@ export function createPublishBitbucketCloudPullRequestAction(options: { username: 'x-token-auth', password: ctx.input.token, }; + } else if ( + integrationConfig.config.clientId && + integrationConfig.config.clientSecret + ) { + const token = await getBitbucketCloudOAuthToken( + integrationConfig.config.clientId, + integrationConfig.config.clientSecret, + ); + auth = { + username: 'x-token-auth', + password: token, + }; } else { if ( !integrationConfig.config.username || diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/helpers.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/helpers.ts index 2e68d4e8b8..47a212fcab 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/helpers.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/helpers.ts @@ -15,12 +15,28 @@ */ import { Bitbucket } from 'bitbucket'; +import { getBitbucketCloudOAuthToken } from '@backstage/integration'; -export const getBitbucketClient = (config: { +export const getBitbucketClient = async (config: { token?: string; username?: string; appPassword?: string; + clientId?: string; + clientSecret?: string; }) => { + // If OAuth credentials provided, fetch token + if (config.clientId && config.clientSecret) { + const token = await getBitbucketCloudOAuthToken( + config.clientId, + config.clientSecret, + ); + return new Bitbucket({ + auth: { + token, + }, + }); + } + if (config.token) { return new Bitbucket({ auth: { @@ -38,15 +54,26 @@ export const getBitbucketClient = (config: { }); } throw new Error( - `Authorization has not been provided for Bitbucket Cloud. Please add either provide a username and token or username and appPassword to the Integrations config`, + `Authorization has not been provided for Bitbucket Cloud. Please provide either OAuth credentials (clientId/clientSecret), username and token, or username and appPassword in the Integrations config`, ); }; -export const getAuthorizationHeader = (config: { +export const getAuthorizationHeader = async (config: { username?: string; appPassword?: string; token?: string; -}) => { + clientId?: string; + clientSecret?: string; +}): Promise => { + // OAuth authentication + if (config.clientId && config.clientSecret) { + const token = await getBitbucketCloudOAuthToken( + config.clientId, + config.clientSecret, + ); + return `Bearer ${token}`; + } + // TODO: appPassword can be removed once fully // deprecated by BitBucket on 9th June 2026. if (config.username && (config.token ?? config.appPassword)) { @@ -62,6 +89,6 @@ export const getAuthorizationHeader = (config: { } throw new Error( - `Authorization has not been provided for Bitbucket Cloud. Please add either provide a username and token or username and appPassword to the Integrations config`, + `Authorization has not been provided for Bitbucket Cloud. Please provide either OAuth credentials (clientId/clientSecret), username and token, or username and appPassword in the Integrations config`, ); }; diff --git a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md index 718306802a..1591b4f1df 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket-server +## 0.2.16 + +### Patch Changes + +- 5a6aca2: Improve error message when provided target branch is missing +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.2.16-next.1 + +### Patch Changes + +- 5a6aca2: Improve error message when provided target branch is missing +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 0.2.16-next.0 + +### Patch Changes + +- 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 + ## 0.2.15 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket-server/package.json b/plugins/scaffolder-backend-module-bitbucket-server/package.json index 96c20d1330..98544c3baa 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/package.json +++ b/plugins/scaffolder-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket-server", - "version": "0.2.15", + "version": "0.2.16", "description": "The Bitbucket Server module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.test.ts b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.test.ts index 37df535fec..1c7b0372f3 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.test.ts @@ -399,4 +399,33 @@ describe('publish:bitbucketServer:pull-request', () => { 'https://hosted.bitbucket.com/projects/project/repos/repo/pull-requests/1', ); }); + + it('should throw an error when the target branch is not found', async () => { + server.use( + rest.get( + 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos/repo/branches', + (_, res, ctx) => { + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json(responseOfBranches), + ); + }, + ), + ); + + await expect( + action.handler({ + ...mockContext, + input: { + ...mockContext.input, + repoUrl: 'hosted.bitbucket.com?project=project&repo=repo', + targetBranch: 'non-existent-branch', + sourceBranch: 'develop', + }, + }), + ).rejects.toThrow( + /Target branch 'non-existent-branch' not found in repository project\/repo/, + ); + }); }); diff --git a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.ts b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.ts index 8717a76ae1..ecafe4434c 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.ts +++ b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.ts @@ -380,6 +380,12 @@ export function createPublishBitbucketServerPullRequestAction(options: { apiBaseUrl, }); + if (!toRef) { + throw new InputError( + `Target branch '${finalTargetBranch}' not found in repository ${project}/${repo}. Please ensure the branch exists before creating a pull request.`, + ); + } + let fromRef = await findBranches({ project, repo, diff --git a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md index 928d9890d3..404670d688 100644 --- a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md @@ -1,5 +1,42 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket +## 0.3.17 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.3.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.16-next.1 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 0.3.17-next.0 + +### Patch Changes + +- 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 + ## 0.3.16 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket/package.json b/plugins/scaffolder-backend-module-bitbucket/package.json index 56cc07e9ca..cbbab54143 100644 --- a/plugins/scaffolder-backend-module-bitbucket/package.json +++ b/plugins/scaffolder-backend-module-bitbucket/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket", - "version": "0.3.16", + "version": "0.3.17", "description": "The bitbucket module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md index 83af0015ae..1644c76641 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-scaffolder-backend-module-confluence-to-markdown +## 0.3.16 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.3.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 0.3.16-next.0 + +### Patch Changes + +- 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 + ## 0.3.15 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json index 539ebbd730..177f7ea512 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown", - "version": "0.3.15", + "version": "0.3.16", "description": "The confluence-to-markdown module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md index 47b36aaaac..c518e36f97 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-scaffolder-backend-module-cookiecutter +## 0.3.18 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-defaults@0.14.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.3.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 0.3.18-next.0 + +### Patch Changes + +- 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 + ## 0.3.17 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index 3fa1dbb4c0..5c31103a9e 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-cookiecutter", - "version": "0.3.17", + "version": "0.3.18", "description": "A module for the scaffolder backend that lets you template projects using cookiecutter", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-gcp/CHANGELOG.md b/plugins/scaffolder-backend-module-gcp/CHANGELOG.md index 7fb293a3c7..5294344dc7 100644 --- a/plugins/scaffolder-backend-module-gcp/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gcp/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-scaffolder-backend-module-gcp +## 0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 0.2.16-next.0 + +### Patch Changes + +- 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 + ## 0.2.15 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gcp/package.json b/plugins/scaffolder-backend-module-gcp/package.json index abc6f428ed..bb50ebd277 100644 --- a/plugins/scaffolder-backend-module-gcp/package.json +++ b/plugins/scaffolder-backend-module-gcp/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gcp", - "version": "0.2.15", + "version": "0.2.16", "description": "The GCP Bucket module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md index b1210fb42a..cc45967b3b 100644 --- a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-scaffolder-backend-module-gerrit +## 0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 0.2.16-next.0 + +### Patch Changes + +- 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 + ## 0.2.15 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gerrit/package.json b/plugins/scaffolder-backend-module-gerrit/package.json index 89b9f3fab6..62d4ebb286 100644 --- a/plugins/scaffolder-backend-module-gerrit/package.json +++ b/plugins/scaffolder-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gerrit", - "version": "0.2.15", + "version": "0.2.16", "description": "The gerrit module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md index 8fc650031e..46852ad119 100644 --- a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-scaffolder-backend-module-gitea +## 0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 0.2.16-next.0 + +### Patch Changes + +- 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 + ## 0.2.15 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gitea/package.json b/plugins/scaffolder-backend-module-gitea/package.json index 54626ff625..21e7d4c98a 100644 --- a/plugins/scaffolder-backend-module-gitea/package.json +++ b/plugins/scaffolder-backend-module-gitea/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitea", - "version": "0.2.15", + "version": "0.2.16", "description": "The gitea module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-github/CHANGELOG.md b/plugins/scaffolder-backend-module-github/CHANGELOG.md index ee6d97310c..0a636abb22 100644 --- a/plugins/scaffolder-backend-module-github/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-github/CHANGELOG.md @@ -1,5 +1,44 @@ # @backstage/plugin-scaffolder-backend-module-github +## 0.9.3 + +### Patch Changes + +- dab3d3f: Added autocompletion resource `owners` for retrieving GitHub repository owners. +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.9.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 0.9.3-next.0 + +### Patch Changes + +- 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 + ## 0.9.2 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-github/package.json b/plugins/scaffolder-backend-module-github/package.json index f9aff4631d..4f58dc4eee 100644 --- a/plugins/scaffolder-backend-module-github/package.json +++ b/plugins/scaffolder-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-github", - "version": "0.9.2", + "version": "0.9.3", "description": "The github module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-github/src/autocomplete/autocomplete.test.ts b/plugins/scaffolder-backend-module-github/src/autocomplete/autocomplete.test.ts index 9b9aaff9c2..8076a4de01 100644 --- a/plugins/scaffolder-backend-module-github/src/autocomplete/autocomplete.test.ts +++ b/plugins/scaffolder-backend-module-github/src/autocomplete/autocomplete.test.ts @@ -31,6 +31,9 @@ const mockOctokit = { listForAuthenticatedUser: jest.fn(), listBranches: jest.fn(), }, + orgs: { + listForAuthenticatedUser: jest.fn(), + }, }, }; jest.mock('octokit', () => ({ @@ -95,6 +98,30 @@ describe('handleAutocompleteRequest', () => { }); }); + it('should return owners', async () => { + const handleAutocompleteRequest = createHandleAutocompleteRequest({ + integrations: mockIntegrations, + }); + + mockOctokit.rest.orgs.listForAuthenticatedUser.mockResolvedValue({ + data: [ + { + login: 'backstage', + }, + ], + }); + + const result = await handleAutocompleteRequest({ + resource: 'owners', + token: 'token', + context: {}, + }); + + expect(result).toEqual({ + results: [{ id: 'backstage' }], + }); + }); + it('should throw an error for invalid resource', async () => { const handleAutocompleteRequest = createHandleAutocompleteRequest({ integrations: mockIntegrations, diff --git a/plugins/scaffolder-backend-module-github/src/autocomplete/autocomplete.ts b/plugins/scaffolder-backend-module-github/src/autocomplete/autocomplete.ts index 35d6b4f930..b3efde9333 100644 --- a/plugins/scaffolder-backend-module-github/src/autocomplete/autocomplete.ts +++ b/plugins/scaffolder-backend-module-github/src/autocomplete/autocomplete.ts @@ -64,6 +64,15 @@ export function createHandleAutocompleteRequest(options: { return { results }; } + case 'owners': { + const orgs = await client.paginate( + client.rest.orgs.listForAuthenticatedUser, + ); + + const results = orgs.map(r => ({ id: r.login })); + + return { results }; + } default: throw new InputError(`Invalid resource: ${resource}`); } diff --git a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md index e9b39fc8bd..41a8c18bd9 100644 --- a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,44 @@ # @backstage/plugin-scaffolder-backend-module-gitlab +## 0.11.0 + +### Minor Changes + +- f2d034b: In the `gitlabRepoPush` action, add 'auto' possibility for `commitAction` input. + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.11.0-next.1 + +### Minor Changes + +- f2d034b: In the `gitlabRepoPush` action, add 'auto' possibility for `commitAction` input. + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 0.10.1-next.0 + +### Patch Changes + +- 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 + ## 0.10.0 ### Minor Changes diff --git a/plugins/scaffolder-backend-module-gitlab/package.json b/plugins/scaffolder-backend-module-gitlab/package.json index 38389e3a16..a447da9417 100644 --- a/plugins/scaffolder-backend-module-gitlab/package.json +++ b/plugins/scaffolder-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitlab", - "version": "0.10.0", + "version": "0.11.0", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-backend-module-gitlab/report.api.md b/plugins/scaffolder-backend-module-gitlab/report.api.md index 1da4c704b1..2ff099d469 100644 --- a/plugins/scaffolder-backend-module-gitlab/report.api.md +++ b/plugins/scaffolder-backend-module-gitlab/report.api.md @@ -130,7 +130,7 @@ export const createGitlabRepoPushAction: (options: { sourcePath?: string | undefined; targetPath?: string | undefined; token?: string | undefined; - commitAction?: 'update' | 'delete' | 'create' | undefined; + commitAction?: 'auto' | 'update' | 'delete' | 'create' | undefined; }, { projectid: string; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts index 66c72b4c46..b345b33ee1 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts @@ -31,57 +31,11 @@ import { import path from 'path'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { InputError } from '@backstage/errors'; -import { - LoggerService, - resolveSafeChildPath, -} from '@backstage/backend-plugin-api'; +import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { createGitlabApi, getErrorMessage } from './helpers'; import { examples } from './gitlabMergeRequest.examples'; -import { createHash } from 'crypto'; -function computeSha256(file: SerializedFile): string { - const hash = createHash('sha256'); - hash.update(file.content); - return hash.digest('hex'); -} - -async function getFileAction( - fileInfo: { file: SerializedFile; targetPath?: string }, - target: { repoID: string; branch: string }, - api: InstanceType, - logger: LoggerService, - remoteFiles: RepositoryTreeSchema[], - defaultCommitAction: - | 'create' - | 'delete' - | 'update' - | 'skip' - | 'auto' = 'auto', -): Promise<'create' | 'delete' | 'update' | 'skip'> { - if (defaultCommitAction === 'auto') { - const filePath = path.join(fileInfo.targetPath ?? '', fileInfo.file.path); - - if (remoteFiles?.some(remoteFile => remoteFile.path === filePath)) { - try { - const targetFile = await api.RepositoryFiles.show( - target.repoID, - filePath, - target.branch, - ); - if (computeSha256(fileInfo.file) === targetFile.content_sha256) { - return 'skip'; - } - } catch (error) { - logger.warn( - `Unable to retrieve detailed information for remote file ${filePath}`, - ); - } - return 'update'; - } - return 'create'; - } - return defaultCommitAction; -} +import { getFileAction } from '../util'; async function getReviewersFromApprovalRules( api: InstanceType, diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts index a3ae415704..dd8e28d87a 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts @@ -26,6 +26,9 @@ import { import { CommitAction } from '@gitbeaker/rest'; import { createGitlabApi, getErrorMessage } from './helpers'; import { examples } from './gitlabRepoPush.examples'; +import { getFileAction } from '../util'; +import { SerializedFile } from '@backstage/plugin-scaffolder-node'; +import { RepositoryTreeSchema } from '@gitbeaker/rest'; /** * Create a new action that commits into a gitlab repository. @@ -75,7 +78,7 @@ export const createGitlabRepoPushAction = (options: { .optional(), commitAction: z => z - .enum(['create', 'update', 'delete'], { + .enum(['create', 'update', 'delete', 'auto'], { description: 'The action to be used for git commit. Defaults to create, but can be set to update or delete', }) @@ -126,15 +129,50 @@ export const createGitlabRepoPushAction = (options: { gitignore: true, }); - const actions: CommitAction[] = fileContents.map(file => ({ - action: commitAction ?? 'create', - filePath: targetPath - ? path.posix.join(targetPath, file.path) - : file.path, - encoding: 'base64', - content: file.content.toString('base64'), - execute_filemode: file.executable, - })); + let remoteFiles: RepositoryTreeSchema[] = []; + if ((ctx.input.commitAction ?? 'auto') === 'auto') { + try { + remoteFiles = await api.Repositories.allRepositoryTrees(repoID, { + ref: branchName, + recursive: true, + path: targetPath ?? undefined, + }); + } catch (e) { + ctx.logger.warn( + `Could not retrieve the list of files for ${repoID} (branch: ${branchName}) : ${getErrorMessage( + e, + )}`, + ); + } + } + + const fileActionMap: { + file: SerializedFile; + action: 'create' | 'delete' | 'update' | 'skip'; + }[] = []; + for (const file of fileContents) { + const action = await getFileAction( + { file, targetPath }, + { repoID, branch: branchName }, + api, + ctx.logger, + remoteFiles, + ctx.input.commitAction, + ); + fileActionMap.push({ file, action }); + } + + const actions: CommitAction[] = fileActionMap + .filter(o => o.action !== 'skip') + .map(({ file, action }) => ({ + action: action as CommitAction['action'], + filePath: targetPath + ? path.posix.join(targetPath, file.path) + : file.path, + encoding: 'base64', + content: file.content.toString('base64'), + execute_filemode: file.executable, + })); const branchExists = await ctx.checkpoint({ key: `branch.exists.${repoID}.${branchName}`, diff --git a/plugins/scaffolder-backend-module-gitlab/src/util.ts b/plugins/scaffolder-backend-module-gitlab/src/util.ts index db1468b180..4c40a36acc 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/util.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/util.ts @@ -14,15 +14,21 @@ * limitations under the License. */ +import { LoggerService } from '@backstage/backend-plugin-api'; import { InputError } from '@backstage/errors'; import { GitLabIntegration, ScmIntegrationRegistry, } from '@backstage/integration'; -import { Gitlab, GroupSchema } from '@gitbeaker/rest'; +import { Gitlab, GroupSchema, RepositoryTreeSchema } from '@gitbeaker/rest'; import { z } from 'zod'; import commonGitlabConfig from './commonGitlabConfig'; +import { SerializedFile } from '@backstage/plugin-scaffolder-node'; + +import { createHash } from 'crypto'; +import path from 'path'; + export const parseRepoHost = (repoUrl: string): string => { let parsed; try { @@ -184,3 +190,47 @@ export async function checkEpicScope( throw new InputError(`Could not find epic scope: ${error.message}`); } } + +function computeSha256(file: SerializedFile): string { + const hash = createHash('sha256'); + hash.update(file.content); + return hash.digest('hex'); +} + +export async function getFileAction( + fileInfo: { file: SerializedFile; targetPath?: string }, + target: { repoID: string; branch: string }, + api: InstanceType, + logger: LoggerService, + remoteFiles: RepositoryTreeSchema[], + defaultCommitAction: + | 'create' + | 'delete' + | 'update' + | 'skip' + | 'auto' = 'auto', +): Promise<'create' | 'delete' | 'update' | 'skip'> { + if (defaultCommitAction === 'auto') { + const filePath = path.join(fileInfo.targetPath ?? '', fileInfo.file.path); + + if (remoteFiles?.some(remoteFile => remoteFile.path === filePath)) { + try { + const targetFile = await api.RepositoryFiles.show( + target.repoID, + filePath, + target.branch, + ); + if (computeSha256(fileInfo.file) === targetFile.content_sha256) { + return 'skip'; + } + } catch (error) { + logger.warn( + `Unable to retrieve detailed information for remote file ${filePath}`, + ); + } + return 'update'; + } + return 'create'; + } + return defaultCommitAction; +} diff --git a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md index e2702f9e4f..c7100f71f9 100644 --- a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/plugin-scaffolder-backend-module-notifications +## 0.1.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-notifications-node@0.2.22 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.1.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/plugin-notifications-common@0.2.0 + - @backstage/plugin-notifications-node@0.2.22-next.1 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 0.1.17-next.0 + +### Patch Changes + +- 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 + ## 0.1.16 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-notifications/package.json b/plugins/scaffolder-backend-module-notifications/package.json index 98c8982977..89b76a7808 100644 --- a/plugins/scaffolder-backend-module-notifications/package.json +++ b/plugins/scaffolder-backend-module-notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-notifications", - "version": "0.1.16", + "version": "0.1.17", "description": "The notifications backend module for the scaffolder plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-rails/CHANGELOG.md b/plugins/scaffolder-backend-module-rails/CHANGELOG.md index 338d846bb0..48db86578e 100644 --- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md @@ -1,5 +1,40 @@ # @backstage/plugin-scaffolder-backend-module-rails +## 0.5.16 + +### Patch Changes + +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.5.16-next.1 + +### Patch Changes + +- 2bae83a: Bumped dev dependencies `@types/node` +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 0.5.16-next.0 + +### Patch Changes + +- 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 + ## 0.5.15 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index ff9ba1de9c..0a778daeff 100644 --- a/plugins/scaffolder-backend-module-rails/package.json +++ b/plugins/scaffolder-backend-module-rails/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-rails", - "version": "0.5.15", + "version": "0.5.16", "description": "A module for the scaffolder backend that lets you template projects using Rails", "backstage": { "role": "backend-plugin-module", @@ -59,7 +59,7 @@ "@backstage/plugin-scaffolder-node-test-utils": "workspace:^", "@types/command-exists": "^1.2.0", "@types/fs-extra": "^11.0.0", - "@types/node": "^20.16.0", + "@types/node": "^22.13.14", "jest-when": "^3.1.0" } } diff --git a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md index d26bb3c6e0..427c5bcfd0 100644 --- a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-scaffolder-backend-module-sentry +## 0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 0.2.16-next.0 + +### Patch Changes + +- 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 + ## 0.2.15 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-sentry/package.json b/plugins/scaffolder-backend-module-sentry/package.json index b3b3ec5572..d8f8c2dd31 100644 --- a/plugins/scaffolder-backend-module-sentry/package.json +++ b/plugins/scaffolder-backend-module-sentry/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-sentry", - "version": "0.2.15", + "version": "0.2.16", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md index ccca3a8b3b..8174ea2562 100644 --- a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/plugin-scaffolder-backend-module-yeoman +## 0.4.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + - @backstage/plugin-scaffolder-node-test-utils@0.3.6 + +## 0.4.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + - @backstage/plugin-scaffolder-node-test-utils@0.3.6-next.1 + +## 0.4.17-next.0 + +### Patch Changes + +- 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 + ## 0.4.16 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index ef7b748fe5..c57da7a684 100644 --- a/plugins/scaffolder-backend-module-yeoman/package.json +++ b/plugins/scaffolder-backend-module-yeoman/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-yeoman", - "version": "0.4.16", + "version": "0.4.17", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 9c2bd67c10..c640292636 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,112 @@ # @backstage/plugin-scaffolder-backend +## 3.1.0 + +### Minor Changes + +- a4cd405: Add `defaultEnvironment` config to scaffolder to enable more flexible and custom templates. Now it's possible enable access to default parameters and secrets in templates, improving security and reducing complexity. + +### Patch Changes + +- be5972b: Fixed a bug where config was not passed to NunjucksWorkflowRunner, causing defaultEnvironment to be undefined +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 2bae83a: Updated `isolated-vm` to `6.0.1` +- 25b560e: Internal change to support new versions of the `logform` library +- 8f4aded: Fixing OpenAPI definition +- 1226647: Updated dependency `esbuild` to `^0.27.0`. +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-gitlab@0.11.0 + - @backstage/integration@1.19.0 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.5 + - @backstage/backend-defaults@0.14.0 + - @backstage/backend-openapi-utils@0.6.4 + - @backstage/plugin-events-node@0.4.18 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-backend-module-github@0.9.3 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.17 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15 + - @backstage/plugin-catalog-node@1.20.1 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.16 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.16 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.16 + - @backstage/plugin-scaffolder-common@1.7.4 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 3.1.0-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 2bae83a: Updated `isolated-vm` to `6.0.1` +- 25b560e: Internal change to support new versions of the `logform` library +- 1226647: Updated dependency `esbuild` to `^0.27.0`. +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-gitlab@0.11.0-next.1 + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/backend-openapi-utils@0.6.4-next.1 + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.16-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-bitbucket-cloud-common@0.3.5-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.15-next.1 + - @backstage/plugin-catalog-node@1.20.1-next.1 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.16-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.17-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.16-next.1 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.16-next.1 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.16-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.9.3-next.1 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 3.1.0-next.0 + +### Minor Changes + +- 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 + ## 3.0.1 ### Patch Changes diff --git a/plugins/scaffolder-backend/README.md b/plugins/scaffolder-backend/README.md index 34a378ba44..3c4a2cb283 100644 --- a/plugins/scaffolder-backend/README.md +++ b/plugins/scaffolder-backend/README.md @@ -64,6 +64,67 @@ you will not have any templates available to use. These need to be [added to the To get up and running and try out some templates quickly, you can or copy the catalog locations from the [create-app template](https://github.com/backstage/backstage/blob/master/packages/create-app/templates/default-app/app-config.yaml.hbs). +## Configuration + +### 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. + ## Audit Events The Scaffolder backend emits audit events for various operations. Events are grouped logically by `eventId`, with `subEventId` providing further distinction when needed. diff --git a/plugins/scaffolder-backend/config.d.ts b/plugins/scaffolder-backend/config.d.ts index b4fb532901..866ededb50 100644 --- a/plugins/scaffolder-backend/config.d.ts +++ b/plugins/scaffolder-backend/config.d.ts @@ -108,5 +108,26 @@ export interface Config { auditor?: { taskParameterMaxLength?: number; }; + + /** + * Default environment variables and secrets available to all templates. + */ + defaultEnvironment?: { + /** + * Default parameters accessible via ${{ environment.parameters.* }} in templates. + */ + parameters?: { + [key: string]: string; + }; + + /** + * Secret values from environment variables accessible via ${{ environment.secrets.* }} in templates. + * Values should reference environment variables like ${SECRET_NAME}. + * @visibility secret + */ + secrets?: { + [key: string]: string; + }; + }; }; } diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index e149badea2..aefcc84895 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "3.0.1", + "version": "3.1.0", "description": "The Backstage backend plugin that helps you create new things", "backstage": { "role": "backend-plugin", @@ -90,11 +90,11 @@ "@opentelemetry/api": "^1.9.0", "@types/luxon": "^3.0.0", "concat-stream": "^2.0.0", - "express": "^4.17.1", + "express": "^4.22.0", "fs-extra": "^11.2.0", "globby": "^11.0.0", "isbinaryfile": "^5.0.0", - "isolated-vm": "^5.0.1", + "isolated-vm": "^6.0.1", "jsonschema": "^1.5.0", "knex": "^3.0.0", "lodash": "^4.17.21", @@ -126,7 +126,7 @@ "@types/nunjucks": "^3.1.4", "@types/supertest": "^2.0.8", "@types/zen-observable": "^0.8.0", - "esbuild": "^0.25.0", + "esbuild": "^0.27.0", "strip-ansi": "^7.1.0", "supertest": "^7.0.0", "wait-for-expect": "^3.0.2" diff --git a/plugins/scaffolder-backend/sample-templates/bitbucket-demo/template.yaml b/plugins/scaffolder-backend/sample-templates/bitbucket-demo/template.yaml index 0a7c841d8f..c8f85974ea 100644 --- a/plugins/scaffolder-backend/sample-templates/bitbucket-demo/template.yaml +++ b/plugins/scaffolder-backend/sample-templates/bitbucket-demo/template.yaml @@ -69,7 +69,7 @@ spec: url: https://github.com/backstage/community/tree/main/backstage-community-sessions - id: publish name: Publish - action: publish:bitbucket + action: publish:bitbucketCloud input: description: This is ${{ parameters.name }} repoUrl: ${{ parameters.repoUrl }} @@ -81,10 +81,28 @@ spec: repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} catalogInfoPath: '/catalog-info.yaml' + - id: create-pr-file + name: Create PR File + action: fetch:plain + input: + targetPath: ./pr-demo + url: https://github.com/backstage/community/blob/main/README.md + - id: create-pr + name: Create Pull Request + action: publish:bitbucketCloud:pull-request + input: + repoUrl: ${{ parameters.repoUrl }} + title: Test PR for ${{ parameters.name }} + description: This is a test pull request created by the scaffolder template to test OAuth support + sourceBranch: scaffolder-pr-${{ parameters.name }} + targetBranch: master + output: links: - title: Repository url: ${{ steps['publish'].output.remoteUrl }} + - title: Pull Request + url: ${{ steps['create-pr'].output.pullRequestUrl }} - title: Open in catalog icon: catalog entityRef: ${{ steps['register'].output.entityRef }} diff --git a/plugins/scaffolder-backend/scripts/Cookiecutter.dockerfile b/plugins/scaffolder-backend/scripts/Cookiecutter.dockerfile index d3b56cdacb..c6bf22ce29 100644 --- a/plugins/scaffolder-backend/scripts/Cookiecutter.dockerfile +++ b/plugins/scaffolder-backend/scripts/Cookiecutter.dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.22 +FROM alpine:3.23 RUN apk add --update \ git \ diff --git a/plugins/scaffolder-backend/src/lib/defaultEnvironment.test.ts b/plugins/scaffolder-backend/src/lib/defaultEnvironment.test.ts new file mode 100644 index 0000000000..c47863e0a2 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/defaultEnvironment.test.ts @@ -0,0 +1,87 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ConfigReader } from '@backstage/config'; +import { resolveDefaultEnvironment } from './defaultEnvironment'; + +describe('defaultEnvironment', () => { + describe('resolveDefaultEnvironment', () => { + it('should return empty when no defaultEnvironment config is provided', () => { + const config = new ConfigReader({}); + const result = resolveDefaultEnvironment(config); + + expect(result).toEqual({ + parameters: {}, + secrets: {}, + }); + }); + + it('should resolve parameters and secrets from config', () => { + const config = new ConfigReader({ + scaffolder: { + defaultEnvironment: { + parameters: { + region: 'us-east-1', + organizationName: 'acme-corp', + version: '1.0.0', + }, + secrets: { + AWS_ACCESS_KEY: 'test-secret-value', + DATABASE_PASSWORD: 'db-password', + LITERAL_SECRET: 'literal-value', + }, + }, + }, + }); + + const result = resolveDefaultEnvironment(config); + + expect(result).toEqual({ + parameters: { + region: 'us-east-1', + organizationName: 'acme-corp', + version: '1.0.0', + }, + secrets: { + AWS_ACCESS_KEY: 'test-secret-value', + DATABASE_PASSWORD: 'db-password', + LITERAL_SECRET: 'literal-value', + }, + }); + }); + + it('should handle only parameters without secrets', () => { + const config = new ConfigReader({ + scaffolder: { + defaultEnvironment: { + parameters: { + region: 'eu-west-1', + }, + }, + }, + }); + + const result = resolveDefaultEnvironment(config); + + expect(result).toEqual({ + parameters: { + region: 'eu-west-1', + }, + secrets: {}, + }); + }); + }); +}); diff --git a/plugins/scaffolder-backend/src/lib/defaultEnvironment.ts b/plugins/scaffolder-backend/src/lib/defaultEnvironment.ts new file mode 100644 index 0000000000..cfb47a0130 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/defaultEnvironment.ts @@ -0,0 +1,66 @@ +/* + * 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 { JsonObject } from '@backstage/types'; + +export interface DefaultEnvironmentConfig { + parameters?: Record; + secrets?: Record; +} + +export interface ResolvedDefaultEnvironment { + parameters: JsonObject; + secrets: Record; +} + +export function resolveDefaultEnvironment( + config: Config, +): ResolvedDefaultEnvironment { + const defaultEnvConfig = config.getOptionalConfig( + 'scaffolder.defaultEnvironment', + ); + if (!defaultEnvConfig) { + return { + parameters: {}, + secrets: {}, + }; + } + + const parameters: JsonObject = {}; + const secrets: Record = {}; + + const parametersConfig = defaultEnvConfig.getOptionalConfig('parameters'); + if (parametersConfig) { + for (const paramKey of parametersConfig.keys()) { + const paramValue = parametersConfig.getString(paramKey); + parameters[paramKey] = paramValue; + } + } + + const secretsConfig = defaultEnvConfig.getOptionalConfig('secrets'); + if (secretsConfig) { + for (const secretKey of secretsConfig.keys()) { + const secretValue = secretsConfig.getString(secretKey); + secrets[secretKey] = secretValue; + } + } + + return { + parameters, + secrets, + }; +} diff --git a/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts b/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts index 43c5aea40f..8b6d909752 100644 --- a/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts @@ -20,6 +20,7 @@ import { LoggerService, } from '@backstage/backend-plugin-api'; import type { UserEntity } from '@backstage/catalog-model'; +import { Config } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { @@ -79,6 +80,7 @@ export type TemplateTesterCreateOptions = { additionalTemplateFilters?: Record; additionalTemplateGlobals?: Record; permissions?: PermissionEvaluator; + config?: Config; }; /** @@ -105,6 +107,7 @@ export function createDryRunner(options: TemplateTesterCreateOptions) { }, }), ]), + config: options.config, }); // Extracting contentsPath and dryRunId from the baseUrl diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts index 2c661fab56..07a25b9e0e 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts @@ -57,6 +57,16 @@ describe('NunjucksWorkflowRunner', () => { const integrations = ScmIntegrations.fromConfig( new ConfigReader({ + scaffolder: { + defaultEnvironment: { + parameters: { + region: 'us-east-1', + }, + secrets: { + AWS_ACCESS_KEY: 'test-secret-value', + }, + }, + }, integrations: { github: [{ host: 'github.com', token: 'token' }], }, @@ -219,12 +229,26 @@ describe('NunjucksWorkflowRunner', () => { { result: AuthorizeResult.ALLOW }, ]); + const config = new ConfigReader({ + scaffolder: { + defaultEnvironment: { + parameters: { + region: 'us-east-1', + }, + secrets: { + AWS_ACCESS_KEY: 'test-secret-value', + }, + }, + }, + }); + runner = new NunjucksWorkflowRunner({ actionRegistry, integrations, workingDirectory: mockDir.path, logger, permissions: mockedPermissionApi, + config, }); }); @@ -501,6 +525,7 @@ describe('NunjucksWorkflowRunner', () => { action: 'jest-mock-action', input: { foo: '${{parameters.input | lower }}', + region: '${{environment.parameters.region}}', }, }, ], @@ -512,7 +537,9 @@ describe('NunjucksWorkflowRunner', () => { await runner.execute(task); expect(fakeActionHandler).toHaveBeenCalledWith( - expect.objectContaining({ input: { foo: 'backstage' } }), + expect.objectContaining({ + input: { foo: 'backstage', region: 'us-east-1' }, + }), ); }); @@ -784,6 +811,49 @@ describe('NunjucksWorkflowRunner', () => { expectTaskLog('info: ***'); }); + // eslint-disable-next-line jest/expect-expect + it('should redact secrets that are passed in the environment', async () => { + actionRegistry.register({ + id: 'log-secret', + description: 'Mock action for testing', + supportsDryRun: true, + handler: async ctx => { + ctx.logger.info(ctx.input.secret); + }, + schema: { + input: { + type: 'object', + required: ['secret'], + properties: { + secret: { + type: 'string', + }, + }, + }, + }, + }); + + const task = createMockTaskWithSpec( + { + steps: [ + { + id: 'test', + name: 'name', + action: 'log-secret', + input: { + secret: '${{ environment.secrets.AWS_ACCESS_KEY }}', + }, + }, + ], + }, + { secret: 'my-secret-value' }, + ); + + await runner.execute(task); + + expectTaskLog('info: ***'); + }); + // eslint-disable-next-line jest/expect-expect it('should redact meta fields properly', async () => { actionRegistry.register({ @@ -1090,7 +1160,9 @@ describe('NunjucksWorkflowRunner', () => { await runner.execute(task); expect(fakeActionHandler).toHaveBeenCalledWith( - expect.objectContaining({ secrets: { foo: 'bar' } }), + expect.objectContaining({ + secrets: { foo: 'bar' }, + }), ); }); @@ -1104,6 +1176,7 @@ describe('NunjucksWorkflowRunner', () => { action: 'jest-mock-action', input: { b: '${{ secrets.foo }}', + aws_key: '${{ environment.secrets.AWS_ACCESS_KEY }}', }, }, ], @@ -1114,7 +1187,41 @@ describe('NunjucksWorkflowRunner', () => { await runner.execute(task); expect(fakeActionHandler).toHaveBeenCalledWith( - expect.objectContaining({ input: { b: 'bar' } }), + expect.objectContaining({ + input: { b: 'bar', aws_key: 'test-secret-value' }, + }), + ); + }); + + it('should separate task secrets from environment secrets', async () => { + const task = createMockTaskWithSpec( + { + steps: [ + { + id: 'test', + name: 'name', + action: 'jest-mock-action', + input: { + b: '${{ secrets.foo }}', + aws_key: '${{ secrets.AWS_ACCESS_KEY }}', + env_aws_key: '${{ environment.secrets.AWS_ACCESS_KEY }}', + }, + }, + ], + }, + { foo: 'bar', AWS_ACCESS_KEY: 'another-value-from-task' }, + ); + + await runner.execute(task); + + expect(fakeActionHandler).toHaveBeenCalledWith( + expect.objectContaining({ + input: { + b: 'bar', + aws_key: 'another-value-from-task', + env_aws_key: 'test-secret-value', + }, + }), ); }); @@ -1133,6 +1240,7 @@ describe('NunjucksWorkflowRunner', () => { ], output: { b: '${{ secrets.foo }}', + c: '${{ environment.secrets.AWS_ACCESS_KEY }}', }, }, { foo: 'bar' }, @@ -1141,6 +1249,7 @@ describe('NunjucksWorkflowRunner', () => { const executedTask = await runner.execute(task); expect(executedTask.output.b).toBeUndefined(); + expect(executedTask.output.c).toBeUndefined(); }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index e2e36438a1..61cba6cb7a 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -51,6 +51,7 @@ import { createConditionAuthorizer } from '@backstage/plugin-permission-node'; import { actionExecutePermission } from '@backstage/plugin-scaffolder-common/alpha'; import { TaskContext, + TaskSecrets, TemplateAction, TemplateFilter, TemplateGlobal, @@ -64,6 +65,8 @@ import { CheckpointContext, CheckpointState, } from '@backstage/plugin-scaffolder-node/alpha'; +import { Config } from '@backstage/config'; +import { resolveDefaultEnvironment } from '../../lib/defaultEnvironment'; type NunjucksWorkflowRunnerOptions = { workingDirectory: string; @@ -74,10 +77,12 @@ type NunjucksWorkflowRunnerOptions = { additionalTemplateFilters?: Record; additionalTemplateGlobals?: Record; permissions?: PermissionsService; + config?: Config; }; type TemplateContext = { parameters: JsonObject; + environment: JsonObject; EXPERIMENTAL_recovery?: TaskRecovery; steps: { [stepName: string]: { output: { [outputName: string]: JsonValue } }; @@ -103,10 +108,12 @@ const createStepLogger = ({ task, step, rootLogger, + redactions, }: { task: TaskContext; step: TaskStep; rootLogger: LoggerService; + redactions?: Record; }) => { const taskLogger = WinstonLogger.create({ level: process.env.LOG_LEVEL || 'info', @@ -117,7 +124,7 @@ const createStepLogger = ({ transports: [new BackstageLoggerTransport(rootLogger, task, step.id)], }); - taskLogger.addRedactions(Object.values(task.secrets ?? {})); + taskLogger.addRedactions(Object.values(redactions ?? {})); return { taskLogger }; }; @@ -129,6 +136,10 @@ const isActionAuthorized = createConditionAuthorizer( export class NunjucksWorkflowRunner implements WorkflowRunner { private readonly defaultTemplateFilters: Record; private readonly options: NunjucksWorkflowRunnerOptions; + private environment: { + parameters: JsonObject; + secrets?: Record; + } = { parameters: {}, secrets: {} }; constructor(options: NunjucksWorkflowRunnerOptions) { this.options = options; @@ -141,6 +152,24 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { private readonly tracker = scaffoldingTracker(); + async getEnvironmentConfig(): Promise<{ + parameters: JsonObject; + secrets?: TaskSecrets; + }> { + if (this.options.config) { + const defaultEnvironment = resolveDefaultEnvironment(this.options.config); + return { + parameters: defaultEnvironment.parameters, + secrets: defaultEnvironment.secrets, + }; + } + + return { + parameters: {}, + secrets: {}, + }; + } + private isSingleTemplateString(input: string) { const { parser, nodes } = nunjucks as unknown as { parser: { @@ -254,18 +283,33 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { task, step, rootLogger: this.options.logger, + redactions: { + ...task.secrets, + ...this.environment?.secrets, + }, }); if (task.isDryRun) { const redactedSecrets = Object.fromEntries( Object.entries(task.secrets ?? {}).map(secret => [secret[0], '***']), ); + + const redactedEnvironmentSecrets = Object.fromEntries( + Object.entries(this.environment?.secrets ?? {}).map(secret => [ + secret[0], + '***', + ]), + ); const debugInput = (step.input && this.render( step.input, { ...context, + environment: { + parameters: this.environment?.parameters || {}, + secrets: redactedEnvironmentSecrets, + }, secrets: redactedSecrets, }, renderTemplate, @@ -300,7 +344,14 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { step.each && this.render( step.each, - { ...context, secrets: task.secrets ?? {} }, + { + ...context, + environment: { + parameters: this.environment?.parameters || {}, + secrets: this.environment?.secrets ?? {}, + }, + secrets: task?.secrets ?? {}, + }, renderTemplate, ); @@ -322,7 +373,15 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { input: step.input ? this.render( step.input, - { ...context, secrets: task.secrets ?? {}, ...i }, + { + ...context, + environment: { + parameters: this.environment?.parameters ?? {}, + secrets: this.environment?.secrets ?? {}, + }, + secrets: task.secrets ?? {}, + ...i, + }, renderTemplate, ) : {}, @@ -485,6 +544,8 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { const { additionalTemplateFilters, additionalTemplateGlobals } = this.options; + this.environment = await this.getEnvironmentConfig(); + const renderTemplate = await SecureTemplater.loadRenderer({ templateFilters: { ...this.defaultTemplateFilters, @@ -501,6 +562,10 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { const context: TemplateContext = { parameters: task.spec.parameters, + environment: { + parameters: this.environment?.parameters || {}, + secrets: {}, + }, steps: {}, user: task.spec.user, context: { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts index 2b13130908..d162f2abbf 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts @@ -103,6 +103,22 @@ describe('StorageTaskBroker', () => { expect(task.secrets).toEqual(fakeSecrets); }, 10000); + it('should return secrets with priority over defaults', async () => { + const broker = new StorageTaskBroker(storage, logger); + await broker.dispatch(emptyTaskWithFakeSecretsSpec); + const task = await broker.claim(); + + expect(task.secrets).toEqual(fakeSecrets); + }, 10000); + + it('should return all secrets', async () => { + const broker = new StorageTaskBroker(storage, logger); + await broker.dispatch(emptyTaskWithFakeSecretsSpec); + const task = await broker.claim(); + + expect(task.secrets).toEqual({ ...fakeSecrets }); + }, 10000); + it('should complete a task', async () => { const broker = new StorageTaskBroker(storage, logger); const dispatchResult = await broker.dispatch(emptyTaskSpec); @@ -232,7 +248,7 @@ describe('StorageTaskBroker', () => { id: taskId, }), ]), - totalTasks: 13, + totalTasks: 15, }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index 3a15b345db..ef704bc9e0 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -134,6 +134,7 @@ export class TaskWorker { additionalTemplateFilters, additionalTemplateGlobals, permissions, + config, }); return new TaskWorker({ diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts index 6ca24663b6..5958aacf41 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts @@ -84,9 +84,10 @@ export class BackstageLoggerTransport extends Transport { break; default: this.backstageLogger.info(String(message)); + break; } - this.taskContext.emitLog(message, { stepId: this.stepId }); + this.taskContext.emitLog(String(message), { stepId: this.stepId }); callback(); } } @@ -131,7 +132,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; })(), @@ -189,10 +192,13 @@ export class WinstonLogger implements RootLoggerService { const level = info[LEVEL]; const fields = info[SPLAT]; 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) + const extraFields = Object.entries(fields as any) .map( ([key, value]) => `${colorizer.colorize('field', `${key}`)}=${value}`, diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts index 874e32b838..22882e8f71 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts @@ -160,6 +160,10 @@ export type WorkflowResponse = { output: { [key: string]: JsonValue } }; export interface WorkflowRunner { execute(task: TaskContext): Promise; + getEnvironmentConfig?(): Promise<{ + parameters: JsonObject; + secrets?: TaskSecrets; + }>; } export type TaskTrackType = { diff --git a/plugins/scaffolder-backend/src/schema/openapi.yaml b/plugins/scaffolder-backend/src/schema/openapi.yaml index b89f54603c..b26515a71b 100644 --- a/plugins/scaffolder-backend/src/schema/openapi.yaml +++ b/plugins/scaffolder-backend/src/schema/openapi.yaml @@ -113,7 +113,7 @@ components: type: string description: type: string - "'examples'": + examples: type: array items: $ref: '#/components/schemas/ActionExample' @@ -417,7 +417,7 @@ components: $ref: '#/components/schemas/JsonObject' output: $ref: '#/components/schemas/JsonObject' - "'examples'": + examples: type: array items: $ref: '#/components/schemas/ScaffolderUsageExample' @@ -449,7 +449,7 @@ components: $ref: '#/components/schemas/JsonObject' output: $ref: '#/components/schemas/JsonObject' - "'examples'": + examples: type: array items: $ref: '#/components/schemas/ScaffolderUsageExample' diff --git a/plugins/scaffolder-backend/src/schema/openapi/generated/router.ts b/plugins/scaffolder-backend/src/schema/openapi/generated/router.ts index 669dd63878..6401c84964 100644 --- a/plugins/scaffolder-backend/src/schema/openapi/generated/router.ts +++ b/plugins/scaffolder-backend/src/schema/openapi/generated/router.ts @@ -173,7 +173,7 @@ export const spec = { description: { type: 'string', }, - "'examples'": { + examples: { type: 'array', items: { $ref: '#/components/schemas/ActionExample', @@ -588,7 +588,7 @@ export const spec = { }, }, }, - "'examples'": { + examples: { type: 'array', items: { $ref: '#/components/schemas/ScaffolderUsageExample', @@ -636,7 +636,7 @@ export const spec = { }, }, }, - "'examples'": { + examples: { type: 'array', items: { $ref: '#/components/schemas/ScaffolderUsageExample', diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 3894ffb876..690126da6e 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -330,6 +330,7 @@ export async function createRouter( auditor, workingDirectory, permissions, + config, ...templateExtensions, }); diff --git a/plugins/scaffolder-common/CHANGELOG.md b/plugins/scaffolder-common/CHANGELOG.md index ef0287c68d..5ca131e060 100644 --- a/plugins/scaffolder-common/CHANGELOG.md +++ b/plugins/scaffolder-common/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-scaffolder-common +## 1.7.4 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + +## 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 + ## 1.7.3 ### Patch Changes diff --git a/plugins/scaffolder-common/package.json b/plugins/scaffolder-common/package.json index 225a57e965..24898228dc 100644 --- a/plugins/scaffolder-common/package.json +++ b/plugins/scaffolder-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-common", - "version": "1.7.3", + "version": "1.7.4", "description": "Common functionalities for the scaffolder, to be shared between scaffolder and scaffolder-backend plugin", "backstage": { "role": "common-library", diff --git a/plugins/scaffolder-node-test-utils/CHANGELOG.md b/plugins/scaffolder-node-test-utils/CHANGELOG.md index ab563445b0..c22669402f 100644 --- a/plugins/scaffolder-node-test-utils/CHANGELOG.md +++ b/plugins/scaffolder-node-test-utils/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/plugin-scaffolder-node-test-utils +## 0.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-test-utils@1.10.2 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-node@0.12.2 + +## 0.3.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-test-utils@1.10.2-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node@0.12.2-next.1 + +## 0.3.6-next.0 + +### Patch Changes + +- 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 + ## 0.3.5 ### Patch Changes diff --git a/plugins/scaffolder-node-test-utils/package.json b/plugins/scaffolder-node-test-utils/package.json index 65a87d6dc5..89e728f180 100644 --- a/plugins/scaffolder-node-test-utils/package.json +++ b/plugins/scaffolder-node-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-node-test-utils", - "version": "0.3.5", + "version": "0.3.6", "backstage": { "role": "node-library", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-node/CHANGELOG.md b/plugins/scaffolder-node/CHANGELOG.md index 20b231397e..dcb7d78184 100644 --- a/plugins/scaffolder-node/CHANGELOG.md +++ b/plugins/scaffolder-node/CHANGELOG.md @@ -1,5 +1,40 @@ # @backstage/plugin-scaffolder-node +## 0.12.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-scaffolder-common@1.7.4 + +## 0.12.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + +## 0.12.2-next.0 + +### Patch Changes + +- 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 + ## 0.12.1 ### Patch Changes diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json index 7834b676c6..ec82ec8ac5 100644 --- a/plugins/scaffolder-node/package.json +++ b/plugins/scaffolder-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-node", - "version": "0.12.1", + "version": "0.12.2", "description": "The plugin-scaffolder-node module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "node-library", diff --git a/plugins/scaffolder-react/CHANGELOG.md b/plugins/scaffolder-react/CHANGELOG.md index e0a8b16267..363804954b 100644 --- a/plugins/scaffolder-react/CHANGELOG.md +++ b/plugins/scaffolder-react/CHANGELOG.md @@ -1,5 +1,74 @@ # @backstage/plugin-scaffolder-react +## 1.19.4 + +### Patch Changes + +- fb029b6: Updated luxon types +- 5ca461e: Fixed bug where custom `review.name` values were incorrectly formatted by `startCase`, preserving them exactly as written. +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- 9b38f22: Updated dependency `use-immer` to `^0.11.0`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/plugin-permission-react@0.4.39 + - @backstage/plugin-scaffolder-common@1.7.4 + +## 1.19.4-next.2 + +### Patch Changes + +- fb029b6: Updated luxon types +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- 9b38f22: Updated dependency `use-immer` to `^0.11.0`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + +## 1.19.4-next.1 + +### Patch Changes + +- 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 + +## 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 + ## 1.19.3 ### Patch Changes diff --git a/plugins/scaffolder-react/package.json b/plugins/scaffolder-react/package.json index 087e6e2135..3bad0ad4c7 100644 --- a/plugins/scaffolder-react/package.json +++ b/plugins/scaffolder-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-react", - "version": "1.19.3", + "version": "1.19.4", "description": "A frontend library that helps other Backstage plugins interact with the Scaffolder", "backstage": { "role": "web-library", @@ -73,10 +73,10 @@ "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", "@react-hookz/web": "^24.0.0", - "@rjsf/core": "5.23.2", - "@rjsf/material-ui": "5.23.2", - "@rjsf/utils": "5.23.2", - "@rjsf/validator-ajv8": "5.23.2", + "@rjsf/core": "5.24.13", + "@rjsf/material-ui": "5.24.13", + "@rjsf/utils": "5.24.13", + "@rjsf/validator-ajv8": "5.24.13", "@types/json-schema": "^7.0.9", "ajv": "^8.0.1", "ajv-errors": "^3.0.0", @@ -90,7 +90,7 @@ "luxon": "^3.0.0", "qs": "^6.9.4", "react-use": "^17.2.4", - "use-immer": "^0.10.0", + "use-immer": "^0.11.0", "zen-observable": "^0.10.0", "zod": "^3.22.4", "zod-to-json-schema": "^3.20.4" @@ -107,7 +107,6 @@ "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.0.0", "@types/humanize-duration": "^3.18.1", - "@types/luxon": "^3.0.0", "@types/react": "^18.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", diff --git a/plugins/scaffolder-react/report-alpha.api.md b/plugins/scaffolder-react/report-alpha.api.md index 7497902e5b..2839851521 100644 --- a/plugins/scaffolder-react/report-alpha.api.md +++ b/plugins/scaffolder-react/report-alpha.api.md @@ -42,7 +42,7 @@ import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { TemplateGroupFilter } from '@backstage/plugin-scaffolder-react'; import { TemplateParameterSchema } from '@backstage/plugin-scaffolder-react'; import { TemplatePresentationV1beta3 } from '@backstage/plugin-scaffolder-common'; -import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; import { UiSchema } from '@rjsf/utils'; import { WidgetProps } from '@rjsf/utils'; import { z } from 'zod'; diff --git a/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.test.tsx b/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.test.tsx index b5f2afc74d..8c4cdcf6e7 100644 --- a/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.test.tsx +++ b/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.test.tsx @@ -466,6 +466,7 @@ describe('ReviewState', () => { it('should allow custom review name', async () => { const formState = { foo: 'test', + providedAPIs: 'api1,api2', }; const schemas: ParsedTemplateSchema[] = [ @@ -477,7 +478,15 @@ describe('ReviewState', () => { type: 'string', 'ui:backstage': { review: { - name: 'bar', + name: 'Bar', + }, + }, + }, + providedAPIs: { + type: 'string', + 'ui:backstage': { + review: { + name: 'Provided APIs', }, }, }, @@ -495,6 +504,9 @@ describe('ReviewState', () => { expect(queryByRole('row', { name: 'Bar test' })).toBeInTheDocument(); expect(queryByRole('row', { name: 'Foo test' })).not.toBeInTheDocument(); + expect( + queryByRole('row', { name: 'Provided APIs api1,api2' }), + ).toBeInTheDocument(); }); it('should handle options in multiple schemas', async () => { diff --git a/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.tsx b/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.tsx index b5b72dcc98..2588d25cab 100644 --- a/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.tsx +++ b/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.tsx @@ -43,7 +43,7 @@ function processSchema( const name = definitionInSchema?.['ui:backstage']?.review?.name ?? definitionInSchema?.title ?? - key; + formatKey(key); if (definitionInSchema) { const backstageReviewOptions = definitionInSchema['ui:backstage']?.review; @@ -96,12 +96,14 @@ export const ReviewState = (props: ReviewStateProps) => { const schema = findSchemaForKey(key, props.schemas, props.formState); return schema ? processSchema(key, value, schema, props.formState) - : [[key, value]]; + : [[formatKey(key), value]]; }) .filter(prop => prop.length > 0), ); - const options = { - titleFormat: formatKey, - }; - return ; + return ( + key }} + /> + ); }; diff --git a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx index b0746e0672..6a542396de 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx @@ -514,11 +514,12 @@ describe('Stepper', () => { const mockFormData = { firstName: 'John' }; - Object.defineProperty(window, 'location', { - value: { - search: `?formData=${JSON.stringify(mockFormData)}`, - }, - }); + // Use history.replaceState to set the query string (jsdom 27 doesn't allow redefining window.location) + window.history.replaceState( + {}, + '', + `?formData=${JSON.stringify(mockFormData)}`, + ); const { getByRole } = await renderInTestApp( diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index 391388c9b4..14a27c8d2a 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,79 @@ # @backstage/plugin-scaffolder +## 1.35.0 + +### Minor Changes + +- dab3d3f: Added field extension `RepoOwnerPicker` for retrieving GitHub repository owners. + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/plugin-scaffolder-react@1.19.4 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-permission-react@0.4.39 + - @backstage/plugin-scaffolder-common@1.7.4 + - @backstage/plugin-techdocs-react@1.3.6 + +## 1.34.4-next.1 + +### Patch Changes + +- be21c5c: Updated dependency `@rjsf/utils` to `5.24.13`. + Updated dependency `@rjsf/core` to `5.24.13`. + Updated dependency `@rjsf/material-ui` to `5.24.13`. + Updated dependency `@rjsf/validator-ajv8` to `5.24.13`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-scaffolder-react@1.19.4-next.2 + - @backstage/integration@1.18.3-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.7 + - @backstage/plugin-permission-react@0.4.39-next.0 + - @backstage/plugin-scaffolder-common@1.7.4-next.0 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + +## 1.34.4-next.0 + +### Patch Changes + +- 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 + ## 1.34.3 ### Patch Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index d85495c9e4..2d6f52eab4 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "1.34.3", + "version": "1.35.0", "description": "The Backstage plugin that helps you create new things", "backstage": { "role": "frontend-plugin", @@ -60,7 +60,6 @@ "dependencies": { "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", - "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", @@ -82,10 +81,10 @@ "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", "@react-hookz/web": "^24.0.0", - "@rjsf/core": "5.23.2", - "@rjsf/material-ui": "5.23.2", - "@rjsf/utils": "5.23.2", - "@rjsf/validator-ajv8": "5.23.2", + "@rjsf/core": "5.24.13", + "@rjsf/material-ui": "5.24.13", + "@rjsf/utils": "5.24.13", + "@rjsf/validator-ajv8": "5.24.13", "@uiw/react-codemirror": "^4.9.3", "classnames": "^2.2.6", "git-url-parse": "^15.0.0", diff --git a/plugins/scaffolder/report-alpha.api.md b/plugins/scaffolder/report-alpha.api.md index b6c5af9f17..7ffe367327 100644 --- a/plugins/scaffolder/report-alpha.api.md +++ b/plugins/scaffolder/report-alpha.api.md @@ -14,13 +14,13 @@ import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; -import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; +import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { FieldExtensionOptions } from '@backstage/plugin-scaffolder-react'; import { FormField } from '@backstage/plugin-scaffolder-react/alpha'; import { formFieldsApiRef } from '@backstage/plugin-scaffolder-react/alpha'; import type { FormProps as FormProps_2 } from '@rjsf/core'; import { FormProps as FormProps_3 } from '@backstage/plugin-scaffolder-react'; -import { IconComponent } from '@backstage/core-plugin-api'; +import { IconComponent } from '@backstage/frontend-plugin-api'; import { IconLinkVerticalProps } from '@backstage/core-components'; import { JSX as JSX_2 } from 'react'; import { LayoutOptions } from '@backstage/plugin-scaffolder-react'; @@ -28,13 +28,14 @@ import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; import { PathParams } from '@backstage/core-plugin-api'; import { ReviewStepProps } from '@backstage/plugin-scaffolder-react'; -import { RouteRef } from '@backstage/frontend-plugin-api'; +import { RouteRef } from '@backstage/core-plugin-api'; +import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; import { ScaffolderFormDecorator } from '@backstage/plugin-scaffolder-react/alpha'; import { ScaffolderFormFieldsApi } from '@backstage/plugin-scaffolder-react/alpha'; -import { SubRouteRef } from '@backstage/frontend-plugin-api'; +import { SubRouteRef } from '@backstage/core-plugin-api'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { TemplateGroupFilter } from '@backstage/plugin-scaffolder-react'; -import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) const _default: OverridableFrontendPlugin< @@ -50,12 +51,15 @@ const _default: OverridableFrontendPlugin< templatingExtensions: SubRouteRef; }, { - registerComponent: ExternalRouteRef; - viewTechDoc: ExternalRouteRef<{ - name: string; - kind: string; - namespace: string; - }>; + registerComponent: ExternalRouteRef; + viewTechDoc: ExternalRouteRef< + { + name: string; + kind: string; + namespace: string; + }, + true + >; }, { 'api:scaffolder': OverridableExtensionDefinition<{ @@ -175,7 +179,7 @@ const _default: OverridableFrontendPlugin< { title: string; icon: IconComponent; - routeRef: RouteRef; + routeRef: RouteRef_2; }, 'core.nav-item.target', {} @@ -184,7 +188,7 @@ const _default: OverridableFrontendPlugin< params: { title: string; icon: IconComponent; - routeRef: RouteRef; + routeRef: RouteRef_2; }; }>; 'page:scaffolder': OverridableExtensionDefinition<{ @@ -198,7 +202,7 @@ const _default: OverridableFrontendPlugin< | ExtensionDataRef | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; @@ -223,7 +227,7 @@ const _default: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; loader: () => Promise; - routeRef?: RouteRef; + routeRef?: RouteRef_2; }; }>; 'scaffolder-form-field:scaffolder/entity-name-picker': OverridableExtensionDefinition<{ @@ -346,6 +350,21 @@ const _default: OverridableFrontendPlugin< field: () => Promise; }; }>; + 'scaffolder-form-field:scaffolder/repo-owner-picker': OverridableExtensionDefinition<{ + kind: 'scaffolder-form-field'; + name: 'repo-owner-picker'; + config: {}; + configInput: {}; + output: ExtensionDataRef< + () => Promise, + 'scaffolder.form-field-loader', + {} + >; + inputs: {}; + params: { + field: () => Promise; + }; + }>; 'scaffolder-form-field:scaffolder/repo-url-picker': OverridableExtensionDefinition<{ kind: 'scaffolder-form-field'; name: 'repo-url-picker'; @@ -495,6 +514,8 @@ export const scaffolderTranslationRef: TranslationRef< readonly 'fields.repoUrlPicker.repository.title': 'Repositories Available'; readonly 'fields.repoUrlPicker.repository.description': 'The name of the repository'; readonly 'fields.repoUrlPicker.repository.inputTitle': 'Repository'; + readonly 'fields.repoOwnerPicker.title': 'Owner'; + readonly 'fields.repoOwnerPicker.description': 'The owner of the repository'; readonly 'aboutCard.launchTemplate': 'Launch Template'; readonly 'actionsPage.content.emptyState.title': 'No information to display'; readonly 'actionsPage.content.emptyState.description': 'There are no actions installed or there was an issue communicating with backend.'; diff --git a/plugins/scaffolder/report.api.md b/plugins/scaffolder/report.api.md index 27fa47a184..e624b157fa 100644 --- a/plugins/scaffolder/report.api.md +++ b/plugins/scaffolder/report.api.md @@ -4,7 +4,7 @@ ```ts import { ApiHolder } from '@backstage/core-plugin-api'; -import { ApiRef } from '@backstage/core-plugin-api'; +import { ApiRef } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { ComponentType } from 'react'; import { createScaffolderFieldExtension as createScaffolderFieldExtension_2 } from '@backstage/plugin-scaffolder-react'; @@ -391,6 +391,30 @@ export const RepoBranchPickerFieldExtension: FieldExtensionComponent_2< } >; +// @public +export const RepoOwnerPickerFieldExtension: FieldExtensionComponent_2< + string, + { + host?: string | undefined; + requestUserCredentials?: + | { + secretsKey: string; + additionalScopes?: + | { + azure?: string[] | undefined; + github?: string[] | undefined; + gitlab?: string[] | undefined; + bitbucket?: string[] | undefined; + gerrit?: string[] | undefined; + gitea?: string[] | undefined; + } + | undefined; + } + | undefined; + excludedOwners?: string[] | undefined; + } +>; + // @public export const repoPickerValidation: ( value: string, diff --git a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditorBrowser.test.tsx b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditorBrowser.test.tsx index 427f679188..86400fcfde 100644 --- a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditorBrowser.test.tsx +++ b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateEditorBrowser.test.tsx @@ -20,14 +20,6 @@ import { MockFileSystemAccess } from '../../../lib/filesystem/MockFileSystemAcce import { DirectoryEditorProvider } from './DirectoryEditorContext'; import { TemplateEditorBrowser } from './TemplateEditorBrowser'; -Blob.prototype.text = async function text() { - return new Promise(resolve => { - const reader = new FileReader(); - reader.onload = () => resolve(reader.result as string); - reader.readAsText(this); - }); -}; - describe('TemplateEditorBrowser', () => { it('should render files and expand dirs without exploding', async () => { await renderInTestApp( diff --git a/plugins/scaffolder/src/alpha/extensions.tsx b/plugins/scaffolder/src/alpha/extensions.tsx index cb69e691b8..5215fc0968 100644 --- a/plugins/scaffolder/src/alpha/extensions.tsx +++ b/plugins/scaffolder/src/alpha/extensions.tsx @@ -14,10 +14,6 @@ * limitations under the License. */ -import { - compatWrapper, - convertLegacyRouteRef, -} from '@backstage/core-compat-api'; import { ApiBlueprint, createExtensionInput, @@ -45,21 +41,19 @@ export const scaffolderPage = PageBlueprint.makeWithOverrides({ i.get(FormFieldBlueprint.dataRefs.formFieldLoader), ); return originalFactory({ - routeRef: convertLegacyRouteRef(rootRouteRef), + routeRef: rootRouteRef, path: '/create', loader: () => - import('../components/Router/Router').then(m => - compatWrapper( - , - ), - ), + import('../components/Router/Router').then(m => ( + + )), }); }, }); export const scaffolderNavItem = NavItemBlueprint.make({ params: { - routeRef: convertLegacyRouteRef(rootRouteRef), + routeRef: rootRouteRef, title: 'Create...', icon: CreateComponentIcon, }, @@ -133,6 +127,14 @@ export const repoBranchPickerFormField = FormFieldBlueprint.make({ }, }); +export const repoOwnerPickerFormField = FormFieldBlueprint.make({ + name: 'repo-owner-picker', + params: { + field: () => + import('./fields/RepoOwnerPicker').then(m => m.RepoOwnerPicker), + }, +}); + export const scaffolderApi = ApiBlueprint.make({ params: defineParams => defineParams({ diff --git a/plugins/scaffolder/src/alpha/fields/RepoOwnerPicker.ts b/plugins/scaffolder/src/alpha/fields/RepoOwnerPicker.ts new file mode 100644 index 0000000000..059d71fb83 --- /dev/null +++ b/plugins/scaffolder/src/alpha/fields/RepoOwnerPicker.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createFormField } from '@backstage/plugin-scaffolder-react/alpha'; +import { + RepoOwnerPicker as Component, + RepoOwnerPickerFieldSchema, +} from '../../components/fields/RepoOwnerPicker'; + +export const RepoOwnerPicker = createFormField({ + component: Component, + name: 'RepoOwnerPicker', + schema: RepoOwnerPickerFieldSchema, +}); diff --git a/plugins/scaffolder/src/alpha/plugin.tsx b/plugins/scaffolder/src/alpha/plugin.tsx index 3b41aa3893..502aab0bbc 100644 --- a/plugins/scaffolder/src/alpha/plugin.tsx +++ b/plugins/scaffolder/src/alpha/plugin.tsx @@ -14,7 +14,6 @@ * limitations under the License. */ -import { convertLegacyRouteRefs } from '@backstage/core-compat-api'; import { createFrontendPlugin } from '@backstage/frontend-plugin-api'; import { actionsRouteRef, @@ -36,6 +35,7 @@ import { ownedEntityPickerFormField, ownerPickerFormField, repoBranchPickerFormField, + repoOwnerPickerFormField, repoUrlPickerFormField, scaffolderApi, scaffolderNavItem, @@ -60,7 +60,7 @@ const scaffolderEntityIconLink = EntityIconLinkBlueprint.make({ export default createFrontendPlugin({ pluginId: 'scaffolder', info: { packageJson: () => import('../../package.json') }, - routes: convertLegacyRouteRefs({ + routes: { root: rootRouteRef, selectedTemplate: selectedTemplateRouteRef, ongoingTask: scaffolderTaskRouteRef, @@ -68,11 +68,11 @@ export default createFrontendPlugin({ listTasks: scaffolderListTaskRouteRef, edit: editRouteRef, templatingExtensions: templatingExtensionsRouteRef, - }), - externalRoutes: convertLegacyRouteRefs({ + }, + externalRoutes: { registerComponent: registerComponentRouteRef, viewTechDoc: viewTechDocRouteRef, - }), + }, extensions: [ scaffolderApi, scaffolderPage, @@ -89,5 +89,6 @@ export default createFrontendPlugin({ myGroupsPickerFormField, ownedEntityPickerFormField, repoBranchPickerFormField, + repoOwnerPickerFormField, ], }); diff --git a/plugins/scaffolder/src/components/fields/RepoOwnerPicker/DefaultRepoOwnerPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/DefaultRepoOwnerPicker.test.tsx new file mode 100644 index 0000000000..2e335a44a3 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/DefaultRepoOwnerPicker.test.tsx @@ -0,0 +1,72 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { fireEvent, screen } from '@testing-library/react'; +import { renderInTestApp } from '@backstage/test-utils'; + +import { DefaultRepoOwnerPicker } from './DefaultRepoOwnerPicker'; + +describe('DefaultRepoOwnerPicker', () => { + it('renders an input field', async () => { + const { getByRole } = await renderInTestApp( + , + ); + + expect(getByRole('textbox')).toBeInTheDocument(); + expect(getByRole('textbox')).toHaveValue('owner1'); + }); + + it('input field disabled', async () => { + await renderInTestApp( + , + ); + + const input = screen.getByRole('textbox'); + + // Expect input to be disabled + expect(input).toBeDisabled(); + expect(input).toHaveValue('owner1'); + }); + + it('calls onChange when the input field changes', async () => { + const onChange = jest.fn(); + + const { getByRole } = await renderInTestApp( + , + ); + + const input = getByRole('textbox'); + + fireEvent.change(input, { + target: { value: 'owner2' }, + }); + + expect(onChange).toHaveBeenCalledWith({ owner: 'owner2' }); + }); +}); diff --git a/plugins/scaffolder/src/components/fields/RepoOwnerPicker/DefaultRepoOwnerPicker.tsx b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/DefaultRepoOwnerPicker.tsx new file mode 100644 index 0000000000..48d7da8ba8 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/DefaultRepoOwnerPicker.tsx @@ -0,0 +1,62 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import FormControl from '@material-ui/core/FormControl'; +import FormHelperText from '@material-ui/core/FormHelperText'; +import TextField from '@material-ui/core/TextField'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; + +import { BaseRepoOwnerPickerProps } from './types'; +import { scaffolderTranslationRef } from '../../../translation'; + +/** + * The underlying component that is rendered in the form for the `DefaultRepoOwnerPicker` + * field extension. + * + * @public + * + */ +export const DefaultRepoOwnerPicker = ({ + onChange, + state, + rawErrors, + isDisabled, + required, + schema, +}: BaseRepoOwnerPickerProps) => { + const { owner } = state; + + const { t } = useTranslationRef(scaffolderTranslationRef); + + return ( + 0 && !owner} + > + onChange({ owner: e.target.value })} + value={owner} + /> + + {schema?.description ?? t('fields.repoOwnerPicker.description')} + + + ); +}; diff --git a/plugins/scaffolder/src/components/fields/RepoOwnerPicker/GitHubRepoOwnerPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/GitHubRepoOwnerPicker.test.tsx new file mode 100644 index 0000000000..bfc6c76768 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/GitHubRepoOwnerPicker.test.tsx @@ -0,0 +1,149 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + ScaffolderApi, + scaffolderApiRef, +} from '@backstage/plugin-scaffolder-react'; +import { GitHubRepoOwnerPicker } from './GitHubRepoOwnerPicker'; +import { act, fireEvent, waitFor, screen } from '@testing-library/react'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import userEvent from '@testing-library/user-event'; + +describe('GitHubRepoOwnerPicker', () => { + const scaffolderApiMock: Partial = { + autocomplete: jest + .fn() + .mockResolvedValue({ results: [{ id: 'owner1' }, { id: 'owner2' }] }), + }; + + it('renders an input field', async () => { + const { getByRole } = await renderInTestApp( + + + , + ); + + expect(getByRole('textbox')).toBeInTheDocument(); + expect(getByRole('textbox')).toHaveValue('owner1'); + }); + + it('input field disabled', async () => { + await renderInTestApp( + + + , + ); + + const input = screen.getByRole('textbox'); + + // Expect input to be disabled + expect(input).toBeDisabled(); + expect(input).toHaveValue('owner1'); + }); + + it('calls onChange when the input field changes', async () => { + const onChange = jest.fn(); + + const { getByRole } = await renderInTestApp( + + + , + ); + + const input = getByRole('textbox'); + + act(() => { + input.focus(); + fireEvent.change(input, { + target: { value: 'owner2' }, + }); + input.blur(); + }); + + expect(onChange).toHaveBeenCalledWith({ owner: 'owner2' }); + }); + + it('should populate owners', async () => { + const onChange = jest.fn(); + + const { getByRole, getByText } = await renderInTestApp( + + + , + ); + + // Open the Autocomplete dropdown + const input = getByRole('textbox'); + await userEvent.click(input); + + // Verify that the available owners are shown + await waitFor(() => expect(getByText('owner1')).toBeInTheDocument()); + + // Verify that selecting an option calls onChange + await userEvent.click(getByText('owner1')); + expect(onChange).toHaveBeenCalledWith({ + owner: 'owner1', + }); + }); + + it('should filter out excluded owners', async () => { + const onChange = jest.fn(); + + const { getByRole, getByText } = await renderInTestApp( + + + , + ); + + // Open the Autocomplete dropdown + const input = getByRole('textbox'); + await userEvent.click(input); + + // Verify that the excluded owners are not shown + await waitFor(() => expect(getByText('owner2')).toBeInTheDocument()); + expect(screen.queryByText('owner1')).not.toBeInTheDocument(); + }); +}); diff --git a/plugins/scaffolder/src/components/fields/RepoOwnerPicker/GitHubRepoOwnerPicker.tsx b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/GitHubRepoOwnerPicker.tsx new file mode 100644 index 0000000000..9258ef312d --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/GitHubRepoOwnerPicker.tsx @@ -0,0 +1,113 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useApi } from '@backstage/core-plugin-api'; +import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react'; +import FormControl from '@material-ui/core/FormControl'; +import FormHelperText from '@material-ui/core/FormHelperText'; +import TextField from '@material-ui/core/TextField'; +import Autocomplete from '@material-ui/lab/Autocomplete'; +import { useCallback, useState } from 'react'; +import useDebounce from 'react-use/esm/useDebounce'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; + +import { BaseRepoOwnerPickerProps } from './types'; +import { scaffolderTranslationRef } from '../../../translation'; + +/** + * The underlying component that is rendered in the form for the `GitHubRepoOwnerPicker` + * field extension. + * + * @public + * + */ +export const GitHubRepoOwnerPicker = ({ + onChange, + state, + rawErrors, + accessToken, + isDisabled, + required, + schema, + excludedOwners = [], +}: BaseRepoOwnerPickerProps<{ + accessToken?: string; + excludedOwners?: string[]; +}>) => { + const { host, owner } = state; + + const [availableOwners, setAvailableOwners] = useState([]); + + const scaffolderApi = useApi(scaffolderApiRef); + const { t } = useTranslationRef(scaffolderTranslationRef); + + const updateAvailableOwners = useCallback(() => { + if (!scaffolderApi.autocomplete || !accessToken || !host) { + setAvailableOwners([]); + return; + } + + scaffolderApi + .autocomplete({ + token: accessToken, + resource: 'owners', + context: { host }, + provider: 'github', + }) + .then(({ results }) => { + const owners = results + .map(r => r.id) + .filter(id => !excludedOwners.includes(id)); + + setAvailableOwners(owners); + }) + .catch(() => { + setAvailableOwners([]); + }); + }, [host, accessToken, scaffolderApi, excludedOwners]); + + useDebounce(updateAvailableOwners, 500, [updateAvailableOwners]); + + return ( + 0 && !owner} + > + { + onChange({ owner: newValue || '' }); + }} + disabled={isDisabled} + options={availableOwners} + renderInput={params => ( + + )} + freeSolo + autoSelect + /> + + {schema?.description ?? t('fields.repoOwnerPicker.description')} + + + ); +}; diff --git a/plugins/scaffolder/src/components/fields/RepoOwnerPicker/RepoOwnerPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/RepoOwnerPicker.test.tsx new file mode 100644 index 0000000000..c51d74416b --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/RepoOwnerPicker.test.tsx @@ -0,0 +1,308 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Form } from '@backstage/plugin-scaffolder-react/alpha'; +import validator from '@rjsf/validator-ajv8'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { + scmIntegrationsApiRef, + ScmIntegrationsApi, + scmAuthApiRef, + ScmAuthApi, +} from '@backstage/integration-react'; + +import { + SecretsContextProvider, + scaffolderApiRef, + useTemplateSecrets, + ScaffolderRJSFField, +} from '@backstage/plugin-scaffolder-react'; +import { act, fireEvent, screen } from '@testing-library/react'; +import { RepoOwnerPicker } from './RepoOwnerPicker'; + +describe('RepoOwnerPicker', () => { + const mockIntegrationsApi: Partial = { + byHost: () => ({ type: 'github' }), + }; + + let mockScmAuthApi: Partial; + + beforeEach(() => { + mockScmAuthApi = { + getCredentials: jest.fn().mockResolvedValue({ token: 'abc123' }), + }; + }); + + describe('happy path rendering', () => { + it('should render the repo owner picker with minimal props', async () => { + const onSubmit = jest.fn(); + + const { getByRole, getByText } = await renderInTestApp( + + +
, + }} + onSubmit={onSubmit} + /> +
+
, + ); + + const input = getByRole('textbox'); + const submitButton = getByRole('button'); + + fireEvent.change(input, { target: { value: 'owner1' } }); + + fireEvent.click(submitButton); + + expect(onSubmit).toHaveBeenCalledWith( + expect.objectContaining({ + formData: 'owner1', + }), + expect.anything(), + ); + + expect(getByText('Owner')).toBeInTheDocument(); + expect(getByText('The owner of the repository')).toBeInTheDocument(); + }); + + it('should disable the picker when ui:disabled', async () => { + const onSubmit = jest.fn(); + + await renderInTestApp( + + + , + }} + onSubmit={onSubmit} + /> + + , + ); + + const input = screen.getByRole('textbox'); + + expect(input).toBeDisabled(); + }); + + it('should render properly with title and description', async () => { + const { getByText } = await renderInTestApp( + + + , + }} + /> + + , + ); + + expect(getByText('test title')).toBeInTheDocument(); + expect(getByText('test description')).toBeInTheDocument(); + }); + }); + + describe('requestUserCredentials', () => { + it('should call the scmAuthApi with the correct params', async () => { + const secretsKey = 'testKey'; + + const SecretsComponent = () => { + const { secrets } = useTemplateSecrets(); + const secret = secrets[secretsKey]; + return secret ?
{secret}
: null; + }; + + const { getByText } = await renderInTestApp( + + + , + }} + /> + + + , + ); + + await act(async () => { + // need to wait for the debounce to finish + await new Promise(resolve => setTimeout(resolve, 600)); + }); + + expect(mockScmAuthApi.getCredentials).toHaveBeenCalledWith({ + url: 'https://github.com', + additionalScope: { + repoWrite: true, + customScopes: { + github: ['workflow'], + }, + }, + }); + + expect(getByText('abc123')).toBeInTheDocument(); + }); + + it('should call the scmAuthApi with the correct params if workspace is nested', async () => { + await renderInTestApp( + + + , + }} + /> + + , + ); + + await act(async () => { + // need to wait for the debounce to finish + await new Promise(resolve => setTimeout(resolve, 600)); + }); + + expect(mockScmAuthApi.getCredentials).toHaveBeenCalledWith({ + url: 'https://github.com', + additionalScope: { + repoWrite: true, + }, + }); + }); + + it('should not call the scmAuthApi if secret is available in the state', async () => { + const secretsKey = 'testKey'; + + const SecretsComponent = () => { + const { secrets } = useTemplateSecrets(); + const secret = secrets[secretsKey]; + return secret ?
{secret}
: null; + }; + + const { getByText } = await renderInTestApp( + + + , + }} + /> + + + , + ); + + await act(async () => { + // need to wait for the debounce to finish + await new Promise(resolve => setTimeout(resolve, 600)); + }); + + // as we already have a secret in the state, getCredentials should not be called again. + expect(mockScmAuthApi.getCredentials).toHaveBeenCalledTimes(0); + + expect(getByText('abc123')).toBeInTheDocument(); + }); + }); +}); diff --git a/plugins/scaffolder/src/components/fields/RepoOwnerPicker/RepoOwnerPicker.tsx b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/RepoOwnerPicker.tsx new file mode 100644 index 0000000000..bcb1a0001c --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/RepoOwnerPicker.tsx @@ -0,0 +1,138 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useApi } from '@backstage/core-plugin-api'; +import { + scmIntegrationsApiRef, + scmAuthApiRef, +} from '@backstage/integration-react'; +import { useEffect, useState, useCallback, useMemo } from 'react'; +import useDebounce from 'react-use/esm/useDebounce'; +import { useTemplateSecrets } from '@backstage/plugin-scaffolder-react'; + +import { RepoOwnerPickerProps } from './schema'; +import { RepoOwnerPickerState } from './types'; +import { DefaultRepoOwnerPicker } from './DefaultRepoOwnerPicker'; +import { GitHubRepoOwnerPicker } from './GitHubRepoOwnerPicker'; + +/** + * The underlying component that is rendered in the form for the `RepoOwnerPicker` + * field extension. + * + * @public + */ +export const RepoOwnerPicker = (props: RepoOwnerPickerProps) => { + const { uiSchema, onChange, rawErrors, formData, schema, required } = props; + const [state, setState] = useState({ + owner: formData || '', + }); + const excludedOwners = useMemo( + () => uiSchema?.['ui:options']?.excludedOwners ?? [], + [uiSchema], + ); + const { host, owner } = state; + + const integrationApi = useApi(scmIntegrationsApiRef); + const scmAuthApi = useApi(scmAuthApiRef); + + const { secrets, setSecrets } = useTemplateSecrets(); + + useDebounce( + async () => { + const { requestUserCredentials } = uiSchema?.['ui:options'] ?? {}; + + if (!requestUserCredentials || !host) { + return; + } + + // don't show login prompt if secret value is already in state + if (secrets[requestUserCredentials.secretsKey]) { + return; + } + + // user has requested that we use the users credentials + // so lets grab them using the scmAuthApi and pass through + // any additional scopes from the ui:options + const { token } = await scmAuthApi.getCredentials({ + url: `https://${host}`, + additionalScope: { + repoWrite: true, + customScopes: requestUserCredentials.additionalScopes, + }, + }); + + // set the secret using the key provided in the ui:options for use + // in the templating the manifest with ${{ secrets[secretsKey] }} + setSecrets({ [requestUserCredentials.secretsKey]: token }); + }, + 500, + [host, uiSchema], + ); + + useEffect(() => { + if (uiSchema?.['ui:options']?.host) { + const hostUiOption = uiSchema['ui:options'].host; + setState(prevState => ({ ...prevState, host: hostUiOption })); + } + }, [uiSchema]); + + useEffect(() => { + onChange(owner); + }, [owner, onChange]); + + const updateLocalState = useCallback( + (newState: RepoOwnerPickerState) => { + setState(prevState => ({ ...prevState, ...newState })); + }, + [setState], + ); + + const hostType = (host && integrationApi.byHost(host)?.type) ?? null; + + const renderRepoOwnerPicker = () => { + switch (hostType) { + case 'github': + return ( + + ); + default: + return ( + + ); + } + }; + + return renderRepoOwnerPicker(); +}; diff --git a/plugins/scaffolder/src/components/fields/RepoOwnerPicker/index.ts b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/index.ts new file mode 100644 index 0000000000..d93e5e22e5 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/index.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { RepoOwnerPicker } from './RepoOwnerPicker'; +export { + RepoOwnerPickerFieldSchema, + type RepoOwnerPickerUiOptions, + RepoOwnerPickerSchema, +} from './schema'; diff --git a/plugins/scaffolder/src/components/fields/RepoOwnerPicker/schema.ts b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/schema.ts new file mode 100644 index 0000000000..7931ca1eec --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/schema.ts @@ -0,0 +1,86 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { makeFieldSchema } from '@backstage/plugin-scaffolder-react'; + +export const RepoOwnerPickerFieldSchema = makeFieldSchema({ + output: z => z.string(), + uiOptions: z => + z.object({ + host: z.string().optional(), + excludedOwners: z + .array(z.string()) + .optional() + .describe('List of excluded owners'), + requestUserCredentials: z + .object({ + secretsKey: z + .string() + .describe( + 'Key used within the template secrets context to store the credential', + ), + additionalScopes: z + .object({ + gitea: z + .array(z.string()) + .optional() + .describe('Additional Gitea scopes to request'), + gerrit: z + .array(z.string()) + .optional() + .describe('Additional Gerrit scopes to request'), + github: z + .array(z.string()) + .optional() + .describe('Additional GitHub scopes to request'), + gitlab: z + .array(z.string()) + .optional() + .describe('Additional GitLab scopes to request'), + bitbucket: z + .array(z.string()) + .optional() + .describe('Additional BitBucket scopes to request'), + azure: z + .array(z.string()) + .optional() + .describe('Additional Azure scopes to request'), + }) + .optional() + .describe('Additional permission scopes to request'), + }) + .optional() + .describe( + 'If defined will request user credentials to auth against the given SCM platform', + ), + }), +}); + +/** + * The input props that can be specified under `ui:options` for the + * `RepoOwnerPicker` field extension. + */ +export type RepoOwnerPickerUiOptions = NonNullable< + (typeof RepoOwnerPickerFieldSchema.TProps.uiSchema)['ui:options'] +>; + +export type RepoOwnerPickerProps = typeof RepoOwnerPickerFieldSchema.TProps; + +// This has been duplicated from /plugins/scaffolder/src/components/fields/RepoUrlPicker/schema.ts +// NOTE: There is a bug with this failing validation in the custom field explorer due +// to https://github.com/rjsf-team/react-jsonschema-form/issues/675 even if +// requestUserCredentials is not defined +export const RepoOwnerPickerSchema = RepoOwnerPickerFieldSchema.schema; diff --git a/plugins/scaffolder/src/components/fields/RepoOwnerPicker/types.ts b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/types.ts new file mode 100644 index 0000000000..eceed58df5 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoOwnerPicker/types.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { RJSFSchema } from '@rjsf/utils'; + +export interface RepoOwnerPickerState { + host?: string; + owner?: string; +} + +export type BaseRepoOwnerPickerProps = T & { + onChange: (state: RepoOwnerPickerState) => void; + state: RepoOwnerPickerState; + rawErrors: string[]; + isDisabled?: boolean; + required?: boolean; + schema?: RJSFSchema; +}; diff --git a/plugins/scaffolder/src/components/fields/VirtualizedListbox.test.tsx b/plugins/scaffolder/src/components/fields/VirtualizedListbox.test.tsx index 59f311fe88..8a058d5320 100644 --- a/plugins/scaffolder/src/components/fields/VirtualizedListbox.test.tsx +++ b/plugins/scaffolder/src/components/fields/VirtualizedListbox.test.tsx @@ -33,7 +33,7 @@ describe('', () => { aria-expanded="true" class="MuiAutocomplete-root MuiAutocomplete-hasClearIcon MuiAutocomplete-hasPopupIcon" role="combobox" - style="position: relative; height: 18px; width: 100%; overflow: auto; will-change: transform; direction: ltr;" + style="position: relative; height: 18px; width: 100%; overflow: auto; -webkit-overflow-scrolling: touch; will-change: transform; direction: ltr;" >
', () => {
', () => {
', () => {
', () => {
{ fields: { title: `${highlightTags.pre}testTitle${highlightTags.post}`, text: `${highlightTags.pre}testText${highlightTags.post}`, - location: `${highlightTags.pre}test/location${highlightTags.post}`, + location: `${highlightTags.pre}test${highlightTags.post}/location`, }, }, rank: 1, diff --git a/plugins/search-backend-node/src/engines/LunrSearchEngineIndexer.test.ts b/plugins/search-backend-node/src/engines/LunrSearchEngineIndexer.test.ts index ff76f0fa7c..46f0317d33 100644 --- a/plugins/search-backend-node/src/engines/LunrSearchEngineIndexer.test.ts +++ b/plugins/search-backend-node/src/engines/LunrSearchEngineIndexer.test.ts @@ -106,4 +106,31 @@ describe('LunrSearchEngineIndexer', () => { ...[lunr.trimmer, lunr.stopWordFilter, lunr.stemmer], ); }); + + it('should tokenize input on non-alphanumeric characters', () => { + const input = + "Tokenize_test string, entity-name. Doesn't break abc123def - also Unicode support also!三 stjärna عربي"; + const expectedTokens = [ + 'tokenize', + 'test', + 'string', + 'entity', + 'name', + 'doesn', + 't', + 'break', + 'abc123def', + 'also', + 'unicode', + 'support', + 'also', + '三', + 'stjärna', + 'عربي', + ]; + + const tokens = lunr.tokenizer(input).map(token => token.toString()); + + expect(tokens).toEqual(expectedTokens); + }); }); diff --git a/plugins/search-backend-node/src/engines/LunrSearchEngineIndexer.ts b/plugins/search-backend-node/src/engines/LunrSearchEngineIndexer.ts index e723957077..ac7f7b1c0d 100644 --- a/plugins/search-backend-node/src/engines/LunrSearchEngineIndexer.ts +++ b/plugins/search-backend-node/src/engines/LunrSearchEngineIndexer.ts @@ -29,8 +29,8 @@ export class LunrSearchEngineIndexer extends BatchSearchEngineIndexer { constructor() { super({ batchSize: 1000 }); - this.builder = new lunr.Builder(); + this.builder.tokenizer.separator = /[^\p{L}\p{N}]+/u; this.builder.pipeline.add(lunr.trimmer, lunr.stopWordFilter, lunr.stemmer); this.builder.searchPipeline.add(lunr.stemmer); this.builder.metadataWhitelist = ['position']; diff --git a/plugins/search-backend/CHANGELOG.md b/plugins/search-backend/CHANGELOG.md index 69a0aaa167..fd4e541411 100644 --- a/plugins/search-backend/CHANGELOG.md +++ b/plugins/search-backend/CHANGELOG.md @@ -1,5 +1,50 @@ # @backstage/plugin-search-backend +## 2.0.9 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/backend-defaults@0.14.0 + - @backstage/backend-openapi-utils@0.6.4 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/backend-plugin-api@1.6.0 + +## 2.0.9-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/backend-openapi-utils@0.6.4-next.1 + - @backstage/plugin-permission-node@0.10.7-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.3 + - @backstage/plugin-search-backend-node@1.4.0-next.1 + - @backstage/plugin-search-common@1.2.21 + +## 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 + ## 2.0.8 ### Patch Changes diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index ed9bff7c83..7ddf71a6e3 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend", - "version": "2.0.8", + "version": "2.0.9", "description": "The Backstage backend plugin that provides your backstage app with search", "backstage": { "role": "backend-plugin", @@ -67,7 +67,7 @@ "@backstage/plugin-search-common": "workspace:^", "@backstage/types": "workspace:^", "dataloader": "^2.0.0", - "express": "^4.17.1", + "express": "^4.22.0", "lodash": "^4.17.21", "qs": "^6.10.1", "yn": "^4.0.0", diff --git a/plugins/search-react/CHANGELOG.md b/plugins/search-react/CHANGELOG.md index eaa9cbce14..bf8908c485 100644 --- a/plugins/search-react/CHANGELOG.md +++ b/plugins/search-react/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-search-react +## 1.10.1 + +### Patch Changes + +- 8947a4e: Skip the very first empty search when going to the landing page +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + +## 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 + ## 1.10.0 ### Minor Changes diff --git a/plugins/search-react/package.json b/plugins/search-react/package.json index 7c18e98f47..72924f9d47 100644 --- a/plugins/search-react/package.json +++ b/plugins/search-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-react", - "version": "1.10.0", + "version": "1.10.1", "backstage": { "role": "web-library", "pluginId": "search", diff --git a/plugins/search-react/report-alpha.api.md b/plugins/search-react/report-alpha.api.md index 3ddcde9076..2f7b32ab16 100644 --- a/plugins/search-react/report-alpha.api.md +++ b/plugins/search-react/report-alpha.api.md @@ -10,7 +10,7 @@ import { JSX as JSX_2 } from 'react'; import { ListItemProps } from '@material-ui/core/ListItem'; import { SearchDocument } from '@backstage/plugin-search-common'; import { SearchResult } from '@backstage/plugin-search-common'; -import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) export type BaseSearchResultListItemProps = T & { diff --git a/plugins/search-react/report.api.md b/plugins/search-react/report.api.md index 96b45776bc..d4b7592682 100644 --- a/plugins/search-react/report.api.md +++ b/plugins/search-react/report.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 { ApiRef } from '@backstage/core-plugin-api'; +import { ApiRef } from '@backstage/frontend-plugin-api'; import { AsyncState } from 'react-use/esm/useAsync'; import { AutocompleteProps } from '@material-ui/lab/Autocomplete'; import { Dispatch } from 'react'; diff --git a/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx b/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx index e482dc3a75..d9cd564ae2 100644 --- a/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx +++ b/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx @@ -69,7 +69,7 @@ describe('SearchPagination', () => { expect(screen.getByText('Results per page:')).toBeInTheDocument(); expect(screen.getByText('25')).toBeInTheDocument(); expect(screen.getByText('1-25')).toBeInTheDocument(); - expect(screen.getByLabelText('Next page')).toBeEnabled(); + expect(screen.getByLabelText('Next page')).toBeDisabled(); expect(screen.getByLabelText('Previous page')).toBeDisabled(); }); @@ -176,6 +176,12 @@ describe('SearchPagination', () => { }); it('Set page limit in the context', async () => { + const initialState = { + term: 'a', + types: [], + filters: {}, + }; + await renderInTestApp( { [configApiRef, configApiMock], ]} > - + , @@ -205,7 +211,7 @@ describe('SearchPagination', () => { it('Set page cursor in the context', async () => { const initialState = { - term: '', + term: 'a', types: [], filters: {}, pageCursor: 'MQ==', // page: 1 @@ -253,7 +259,7 @@ describe('SearchPagination', () => { it('Resets page cursor when page limit changes', async () => { const initialState = { - term: '', + term: 'a', types: [], filters: {}, pageCursor: 'Mg==', // page: 2 @@ -280,9 +286,7 @@ describe('SearchPagination', () => { pageCursor: undefined, pageLimit: 10, }), - { - signal: expect.any(AbortSignal), - }, + { signal: expect.any(AbortSignal) }, ); }); }); diff --git a/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.test.tsx b/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.test.tsx index cb274be0a0..0f46f15ed4 100644 --- a/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.test.tsx +++ b/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.test.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { screen, waitFor } from '@testing-library/react'; +import { screen, waitFor, fireEvent } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import MenuItem from '@material-ui/core/MenuItem'; @@ -66,7 +66,7 @@ describe('SearchResultGroup', () => { }); it('Renders without exploding', async () => { - query.mockResolvedValueOnce({ + query.mockResolvedValue({ results, }); @@ -96,7 +96,7 @@ describe('SearchResultGroup', () => { }); it('Renders search results from context', async () => { - query.mockResolvedValueOnce({ + query.mockResolvedValue({ results, }); @@ -107,7 +107,9 @@ describe('SearchResultGroup', () => { [analyticsApiRef, analyticsApiMock], ]} > - + } title="Documentation" @@ -128,7 +130,7 @@ describe('SearchResultGroup', () => { }); it('Renders search results using extensions', async () => { - query.mockResolvedValueOnce({ + query.mockResolvedValue({ results, }); @@ -166,7 +168,7 @@ describe('SearchResultGroup', () => { }); it('Defines a default link', async () => { - query.mockResolvedValueOnce({ + query.mockResolvedValue({ results, }); @@ -190,7 +192,7 @@ describe('SearchResultGroup', () => { }); it('Defines a default render result item', async () => { - query.mockResolvedValueOnce({ + query.mockResolvedValue({ results, }); @@ -221,6 +223,10 @@ describe('SearchResultGroup', () => { }); it('Could be customized with no results text', async () => { + query.mockResolvedValue({ + results: [], + }); + await renderInTestApp( { }); it('Could be customized with filters', async () => { - query.mockResolvedValueOnce({ + query.mockResolvedValue({ results, }); @@ -272,7 +278,7 @@ describe('SearchResultGroup', () => { }); it('Could have a text search filter field', async () => { - query.mockResolvedValueOnce({ + query.mockResolvedValue({ results, }); @@ -312,10 +318,10 @@ describe('SearchResultGroup', () => { await userEvent.click(screen.getByText('owner')); - await userEvent.type( - screen.getByRole('textbox'), - '{backspace}{backspace}{backspace}{backspace}techdocs-core', - ); + // Use fireEvent.blur for contentEditable elements since userEvent.type with + // backspace doesn't work properly in jsdom (jsdom limitation, not a bug) + const textbox = screen.getByRole('textbox'); + fireEvent.blur(textbox, { target: { textContent: 'techdocs-core' } }); await waitFor(() => { expect(screen.getByText('techdocs-core')).toBeInTheDocument(); @@ -323,7 +329,7 @@ describe('SearchResultGroup', () => { }); it('Could have a select search filter field', async () => { - query.mockResolvedValueOnce({ + query.mockResolvedValue({ results, }); @@ -376,7 +382,7 @@ describe('SearchResultGroup', () => { }); it('Shows a progress bar when loading results', async () => { - query.mockReturnValueOnce(new Promise(() => {})); + query.mockReturnValue(new Promise(() => {})); await renderInTestApp( { }); it('Does not render result group if no results returned and disableRenderingWithNoResults prop is provided', async () => { - query.mockResolvedValueOnce({ results: [] }); + query.mockResolvedValue({ results: [] }); await renderInTestApp( { }); it('Should render custom component when no results returned', async () => { - query.mockResolvedValueOnce({ results: [] }); + query.mockResolvedValue({ results: [] }); await renderInTestApp( { }); it('Shows an error panel when results rendering fails', async () => { - query.mockRejectedValueOnce(new Error()); + query.mockRejectedValue(new Error()); await renderInTestApp( ( initialValue.pageCursor, ); + const isFirstEmptyMount = useRef(true); const prevTerm = usePrevious(term); const prevFilters = usePrevious(filters); const abortControllerRef = useRef(null); - const result = useAsync(async () => { + const result = useAsync(async (): Promise => { + if (isFirstEmptyMount.current) { + if (!term && !types.length && !Object.keys(filters).length) { + return { + results: [], + numberOfResults: 0, + }; + } + + isFirstEmptyMount.current = false; + } + // Here we cancel the previous request before making a new one if (abortControllerRef.current) { abortControllerRef.current.abort(); diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index dd4261ea59..8ffeceef6a 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,50 @@ # @backstage/plugin-search +## 1.5.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-search-react@1.10.1 + - @backstage/core-plugin-api@1.12.1 + +## 1.5.1-next.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-search-react@1.10.1-next.0 + +## 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 + ## 1.5.0 ### Minor Changes diff --git a/plugins/search/package.json b/plugins/search/package.json index 48bc3abfb9..35db8a6d35 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search", - "version": "1.5.0", + "version": "1.5.1", "description": "The Backstage plugin that provides your backstage app with search", "backstage": { "role": "frontend-plugin", @@ -58,7 +58,6 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", diff --git a/plugins/search/report-alpha.api.md b/plugins/search/report-alpha.api.md index 5ac036ca68..b129d4ab6c 100644 --- a/plugins/search/report-alpha.api.md +++ b/plugins/search/report-alpha.api.md @@ -3,27 +3,28 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { AnyApiFactory } from '@backstage/core-plugin-api'; +import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; -import { ApiFactory } from '@backstage/core-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; -import { IconComponent } from '@backstage/core-plugin-api'; +import { IconComponent } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; import { RouteRef } from '@backstage/frontend-plugin-api'; +import { RouteRef as RouteRef_2 } from '@backstage/core-plugin-api'; import { SearchFilterExtensionComponent } from '@backstage/plugin-search-react/alpha'; import { SearchResultItemExtensionComponent } from '@backstage/plugin-search-react/alpha'; import { SearchResultItemExtensionPredicate } from '@backstage/plugin-search-react/alpha'; -import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) const _default: OverridableFrontendPlugin< { - root: RouteRef; + root: RouteRef_2; }, {}, { @@ -66,12 +67,10 @@ const _default: OverridableFrontendPlugin< 'page:search': OverridableExtensionDefinition<{ config: { noTrack: boolean; - } & { path: string | undefined; }; configInput: { noTrack?: boolean | undefined; - } & { path?: string | undefined; }; output: @@ -186,12 +185,10 @@ export const searchNavItem: OverridableExtensionDefinition<{ export const searchPage: OverridableExtensionDefinition<{ config: { noTrack: boolean; - } & { path: string | undefined; }; configInput: { noTrack?: boolean | undefined; - } & { path?: string | undefined; }; output: @@ -267,8 +264,8 @@ export const searchTranslationRef: TranslationRef< readonly 'searchType.tabs.allTitle': 'All'; readonly 'searchType.allResults': 'All Results'; readonly 'searchType.accordion.collapse': 'Collapse'; - readonly 'searchType.accordion.allTitle': 'All'; readonly 'searchType.accordion.numberOfResults': '{{number}} results'; + readonly 'searchType.accordion.allTitle': 'All'; readonly 'sidebarSearchModal.title': 'Search'; } >; diff --git a/plugins/search/src/alpha.tsx b/plugins/search/src/alpha.tsx index 97bd7dfe87..2efe9978e0 100644 --- a/plugins/search/src/alpha.tsx +++ b/plugins/search/src/alpha.tsx @@ -68,11 +68,6 @@ import { rootRouteRef } from './plugin'; import { SearchClient } from './apis'; import { SearchType } from './components/SearchType'; import { UrlUpdater } from './components/SearchPage/SearchPage'; -import { - compatWrapper, - convertLegacyRouteRef, - convertLegacyRouteRefs, -} from '@backstage/core-compat-api'; /** @alpha */ export const searchApi = ApiBlueprint.make({ @@ -116,7 +111,7 @@ export const searchPage = PageBlueprint.makeWithOverrides({ factory(originalFactory, { config, inputs }) { return originalFactory({ path: '/search', - routeRef: convertLegacyRouteRef(rootRouteRef), + routeRef: rootRouteRef, loader: async () => { const getResultItemComponent = (result: SearchResult) => { const value = inputs.items.find(item => @@ -254,11 +249,11 @@ export const searchPage = PageBlueprint.makeWithOverrides({ ); }; - return compatWrapper( + return ( - , + ); }, }); @@ -268,7 +263,7 @@ export const searchPage = PageBlueprint.makeWithOverrides({ /** @alpha */ export const searchNavItem = NavItemBlueprint.make({ params: { - routeRef: convertLegacyRouteRef(rootRouteRef), + routeRef: rootRouteRef, title: 'Search', icon: SearchIcon, }, @@ -279,9 +274,9 @@ export default createFrontendPlugin({ pluginId: 'search', info: { packageJson: () => import('../package.json') }, extensions: [searchApi, searchPage, searchNavItem], - routes: convertLegacyRouteRefs({ + routes: { root: rootRouteRef, - }), + }, }); /** @alpha */ diff --git a/plugins/search/src/components/HomePageComponent/HomePageSearchBar.test.tsx b/plugins/search/src/components/HomePageComponent/HomePageSearchBar.test.tsx index e7c47b7d30..270bdbb2eb 100644 --- a/plugins/search/src/components/HomePageComponent/HomePageSearchBar.test.tsx +++ b/plugins/search/src/components/HomePageComponent/HomePageSearchBar.test.tsx @@ -46,12 +46,7 @@ describe('', () => { }, ); - expect(searchApiMock.query).toHaveBeenCalledWith( - expect.objectContaining({ term: '' }), - { - signal: expect.any(AbortSignal), - }, - ); + expect(searchApiMock.query).not.toHaveBeenCalled(); await userEvent.type(screen.getByLabelText('Search'), 'term{enter}'); diff --git a/plugins/search/src/components/SearchModal/SearchModal.test.tsx b/plugins/search/src/components/SearchModal/SearchModal.test.tsx index 8d8e1bb9b9..9424599130 100644 --- a/plugins/search/src/components/SearchModal/SearchModal.test.tsx +++ b/plugins/search/src/components/SearchModal/SearchModal.test.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { screen } from '@testing-library/react'; +import { screen, waitFor } from '@testing-library/react'; import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils'; import userEvent from '@testing-library/user-event'; import { configApiRef } from '@backstage/core-plugin-api'; @@ -63,7 +63,6 @@ describe('SearchModal', () => { ); expect(screen.getByRole('dialog')).toBeInTheDocument(); - expect(searchApiMock.query).toHaveBeenCalledTimes(1); }); it('Should use parent search context if defined', async () => { @@ -106,15 +105,21 @@ describe('SearchModal', () => { ); expect(screen.getByRole('dialog')).toBeInTheDocument(); - expect(searchApiMock.query).toHaveBeenCalledWith( - { - term: '', - filters: {}, - types: [], - pageCursor: undefined, - }, - { signal: expect.any(AbortSignal) }, - ); + + const input = screen.getByLabelText('Search'); + await userEvent.type(input, 'text'); + + await waitFor(() => { + expect(searchApiMock.query).toHaveBeenCalledWith( + { + term: 'text', + filters: {}, + types: [], + pageCursor: undefined, + }, + { signal: expect.any(AbortSignal) }, + ); + }); }); it('Should render a custom Modal correctly', async () => { @@ -146,7 +151,6 @@ describe('SearchModal', () => { }, ); - expect(searchApiMock.query).toHaveBeenCalledTimes(1); await userEvent.keyboard('{Escape}'); expect(toggleModal).toHaveBeenCalledTimes(1); }); diff --git a/plugins/signals-backend/CHANGELOG.md b/plugins/signals-backend/CHANGELOG.md index 0079628280..ba15a7f165 100644 --- a/plugins/signals-backend/CHANGELOG.md +++ b/plugins/signals-backend/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-signals-backend +## 0.3.11 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-events-node@0.4.18 + - @backstage/plugin-signals-node@0.1.27 + - @backstage/backend-plugin-api@1.6.0 + +## 0.3.11-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/plugin-signals-node@0.1.27-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## 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 + ## 0.3.10 ### Patch Changes diff --git a/plugins/signals-backend/package.json b/plugins/signals-backend/package.json index e553d6c594..16ff55a7a4 100644 --- a/plugins/signals-backend/package.json +++ b/plugins/signals-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals-backend", - "version": "0.3.10", + "version": "0.3.11", "backstage": { "role": "backend-plugin", "pluginId": "signals", @@ -42,7 +42,7 @@ "@backstage/plugin-events-node": "workspace:^", "@backstage/plugin-signals-node": "workspace:^", "@backstage/types": "workspace:^", - "express": "^4.17.1", + "express": "^4.22.0", "express-promise-router": "^4.1.0", "uuid": "^11.0.0", "ws": "^8.18.0" diff --git a/plugins/signals-node/CHANGELOG.md b/plugins/signals-node/CHANGELOG.md index 54ecfefc95..89f3cbfc09 100644 --- a/plugins/signals-node/CHANGELOG.md +++ b/plugins/signals-node/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-signals-node +## 0.1.27 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-events-node@0.4.18 + - @backstage/backend-plugin-api@1.6.0 + +## 0.1.27-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-events-node@0.4.18-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/config@1.3.6 + - @backstage/types@1.2.2 + +## 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 + ## 0.1.26 ### Patch Changes diff --git a/plugins/signals-node/package.json b/plugins/signals-node/package.json index e9866f7eda..41df262538 100644 --- a/plugins/signals-node/package.json +++ b/plugins/signals-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals-node", - "version": "0.1.26", + "version": "0.1.27", "description": "Node.js library for the signals plugin", "backstage": { "role": "node-library", @@ -42,7 +42,7 @@ "@backstage/plugin-auth-node": "workspace:^", "@backstage/plugin-events-node": "workspace:^", "@backstage/types": "workspace:^", - "express": "^4.17.1", + "express": "^4.22.0", "uuid": "^11.0.0", "ws": "^8.18.0" }, diff --git a/plugins/signals-react/CHANGELOG.md b/plugins/signals-react/CHANGELOG.md index d2afa41fb6..d8ad46909e 100644 --- a/plugins/signals-react/CHANGELOG.md +++ b/plugins/signals-react/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-signals-react +## 0.0.18 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1 + +## 0.0.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/types@1.2.2 + ## 0.0.17 ### Patch Changes diff --git a/plugins/signals-react/package.json b/plugins/signals-react/package.json index ba4829c19f..debc7647ef 100644 --- a/plugins/signals-react/package.json +++ b/plugins/signals-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals-react", - "version": "0.0.17", + "version": "0.0.18", "description": "Web library for the signals plugin", "backstage": { "role": "web-library", diff --git a/plugins/signals-react/report.api.md b/plugins/signals-react/report.api.md index 87d856288b..23a81af4fc 100644 --- a/plugins/signals-react/report.api.md +++ b/plugins/signals-react/report.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 { ApiRef } from '@backstage/core-plugin-api'; +import { ApiRef } from '@backstage/frontend-plugin-api'; import { JsonObject } from '@backstage/types'; // @public (undocumented) diff --git a/plugins/signals/CHANGELOG.md b/plugins/signals/CHANGELOG.md index b47bd54a35..2e170fd29d 100644 --- a/plugins/signals/CHANGELOG.md +++ b/plugins/signals/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-signals +## 0.0.26 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/plugin-signals-react@0.0.18 + +## 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 + ## 0.0.25 ### Patch Changes diff --git a/plugins/signals/package.json b/plugins/signals/package.json index 4ee2efbe22..6ff62d6943 100644 --- a/plugins/signals/package.json +++ b/plugins/signals/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals", - "version": "0.0.25", + "version": "0.0.26", "backstage": { "role": "frontend-plugin", "pluginId": "signals", @@ -51,7 +51,6 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", diff --git a/plugins/techdocs-addons-test-utils/CHANGELOG.md b/plugins/techdocs-addons-test-utils/CHANGELOG.md index cb47778bc1..21ace9fa17 100644 --- a/plugins/techdocs-addons-test-utils/CHANGELOG.md +++ b/plugins/techdocs-addons-test-utils/CHANGELOG.md @@ -1,5 +1,81 @@ # @backstage/plugin-techdocs-addons-test-utils +## 2.0.0 + +### Major Changes + +- 8d6709e: **BREAKING**: `TechDocsAddonTester.renderWithEffects()` no longer returns a screen; this means that you can no longer grab assertions such as `getByText` from its return value. + + Newer versions of `@testing-library` recommends using the `screen` export for assertions - and removing this from the addon tester contract allows us to more freely iterate on which underlying version of the testing library is being used. + + One notable effect of this, however, is that the `@testing-library` `screen` does NOT support assertions on the shadow DOM, which techdocs relies on. You will therefore want to add a dependency on [the `shadow-dom-testing-library` package](https://github.com/konnorrogers/shadow-dom-testing-library/) in your tests, and using its `screen` and its dedicated `*Shadow*` methods. As an example, if you keep doing `getByText` you will not get matches inside the shadow DOM - switch to `getByShadowText` instead. + + ```ts + import { screen } from 'shadow-dom-testing-library'; + + // ... render the addon ... + await TechDocsAddonTester.buildAddonsInTechDocs([]) + .withDom(TEST_CONTENT) + .renderWithEffects(); + + expect(screen.getByShadowText('TEST_CONTENT')).toBeInTheDocument(); + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/plugin-techdocs@1.16.1 + - @backstage/plugin-catalog@1.32.1 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-search-react@1.10.1 + - @backstage/test-utils@1.7.14 + - @backstage/core-plugin-api@1.12.1 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-techdocs-react@1.3.6 + +## 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 + +## 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 + ## 1.1.2 ### Patch Changes diff --git a/plugins/techdocs-addons-test-utils/package.json b/plugins/techdocs-addons-test-utils/package.json index 7d47cb8720..05ea82584e 100644 --- a/plugins/techdocs-addons-test-utils/package.json +++ b/plugins/techdocs-addons-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-addons-test-utils", - "version": "1.1.2", + "version": "2.0.0", "backstage": { "role": "web-library", "pluginId": "techdocs-addons", @@ -49,7 +49,7 @@ "@backstage/plugin-techdocs": "workspace:^", "@backstage/plugin-techdocs-react": "workspace:^", "@backstage/test-utils": "workspace:^", - "testing-library__dom": "^7.29.4-beta.1" + "shadow-dom-testing-library": "^1.13.1" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/plugins/techdocs-addons-test-utils/report.api.md b/plugins/techdocs-addons-test-utils/report.api.md index a963c239e2..a3326779d7 100644 --- a/plugins/techdocs-addons-test-utils/report.api.md +++ b/plugins/techdocs-addons-test-utils/report.api.md @@ -6,7 +6,6 @@ import { ApiRef } from '@backstage/core-plugin-api'; import { JSXElementConstructor } from 'react'; import { ReactElement } from 'react'; -import { screen as screen_2 } from 'testing-library__dom'; import { TechDocsEntityMetadata } from '@backstage/plugin-techdocs-react'; import { TechDocsMetadata } from '@backstage/plugin-techdocs-react'; @@ -16,11 +15,9 @@ export class TechDocsAddonTester { atPath(path: string): this; build(): ReactElement>; static buildAddonsInTechDocs(addons: ReactElement[]): TechDocsAddonTester; - renderWithEffects(): Promise< - typeof screen_2 & { - shadowRoot: ShadowRoot | null; - } - >; + renderWithEffects(): Promise<{ + shadowRoot: ShadowRoot | null; + }>; withApis(apis: TechdocsAddonTesterApis): this; withDom(dom: ReactElement): this; withEntity(entity: Partial): this; diff --git a/plugins/techdocs-addons-test-utils/src/test-utils.tsx b/plugins/techdocs-addons-test-utils/src/test-utils.tsx index a7cb74cb93..886a03007f 100644 --- a/plugins/techdocs-addons-test-utils/src/test-utils.tsx +++ b/plugins/techdocs-addons-test-utils/src/test-utils.tsx @@ -18,7 +18,7 @@ import { cloneElement, ReactElement } from 'react'; // Shadow DOM support for the simple and complete DOM testing utilities // https://github.com/testing-library/dom-testing-library/issues/742#issuecomment-674987855 -import { screen } from 'testing-library__dom'; +import { screen } from 'shadow-dom-testing-library'; import { Route } from 'react-router-dom'; import { act, render } from '@testing-library/react'; @@ -320,6 +320,25 @@ export class TechDocsAddonTester { * Render the Addon within a fully configured and mocked TechDocs reader. * * @remarks + * + * Note that to make assertions on the shadow dom, add a dependency on + * [the `shadow-dom-testing-library` package](https://github.com/konnorrogers/shadow-dom-testing-library/) + * and use its screen as follows: + * + * ```ts + * import { screen } from 'shadow-dom-testing-library'; + * + * // ... render the addon ... + * await TechDocsAddonTester.buildAddonsInTechDocs([]) + * .withDom(TEST_CONTENT) + * .renderWithEffects(); + * + * expect(screen.getByShadowText('TEST_CONTENT')).toBeInTheDocument(); + * ``` + * + * For items outside of the shadow dom, you can still use the regular screen + * from `@testing-library/react`. + * * Components using useEffect to perform an asynchronous action (such as * fetch) must be rendered within an async act call to properly get the final * state, even with mocked responses. This utility method makes the signature @@ -329,17 +348,16 @@ export class TechDocsAddonTester { * @see https://github.com/testing-library/react-testing-library/issues/281 * @see https://github.com/facebook/react/pull/14853 */ - async renderWithEffects(): Promise< - typeof screen & { shadowRoot: ShadowRoot | null } - > { + async renderWithEffects(): Promise<{ shadowRoot: ShadowRoot | null }> { await act(async () => { render(this.build()); }); - const shadowHost = await screen.findByTestId('techdocs-native-shadowroot'); + const shadowHost = await screen.findByShadowTestId( + 'techdocs-native-shadowroot', + ); return { - ...screen, shadowRoot: shadowHost?.shadowRoot || null, }; } diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index fd8c03fbfa..432c04afbc 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,59 @@ # @backstage/plugin-techdocs-backend +## 2.1.3 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 2bae83a: Corrected `ErrorCallback` type to work with Node 22 types +- 592361e: The `techdocs` config is now marked as optional. +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-defaults@0.14.0 + - @backstage/plugin-techdocs-node@1.13.10 + - @backstage/backend-plugin-api@1.6.0 + - @backstage/plugin-catalog-node@1.20.1 + +## 2.1.3-next.2 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 2bae83a: Corrected `ErrorCallback` type to work with Node 22 types +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/plugin-techdocs-node@1.13.10-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.20.1-next.1 + +## 2.1.3-next.1 + +### Patch Changes + +- 592361e: The `techdocs` config is now marked as optional. + +## 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 + ## 2.1.2 ### Patch Changes diff --git a/plugins/techdocs-backend/config.d.ts b/plugins/techdocs-backend/config.d.ts index 4954392828..1520ad70ec 100644 --- a/plugins/techdocs-backend/config.d.ts +++ b/plugins/techdocs-backend/config.d.ts @@ -21,7 +21,7 @@ export interface Config { * Configuration options for the techdocs-backend plugin * @see http://backstage.io/docs/features/techdocs/configuration */ - techdocs: { + techdocs?: { /** * Documentation building process depends on the builder attr * @visibility frontend diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index d8b1bfbfa1..cf408668d4 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "2.1.2", + "version": "2.1.3", "description": "The Backstage backend plugin that renders technical documentation for your components", "backstage": { "role": "backend-plugin", @@ -68,7 +68,7 @@ "@backstage/plugin-catalog-node": "workspace:^", "@backstage/plugin-techdocs-node": "workspace:^", "@backstage/types": "workspace:^", - "express": "^4.17.1", + "express": "^4.22.0", "express-promise-router": "^4.1.0", "fs-extra": "^11.2.0", "knex": "^3.0.0", diff --git a/plugins/techdocs-backend/src/cache/cacheMiddleware.ts b/plugins/techdocs-backend/src/cache/cacheMiddleware.ts index 78660d3c39..72f910a10a 100644 --- a/plugins/techdocs-backend/src/cache/cacheMiddleware.ts +++ b/plugins/techdocs-backend/src/cache/cacheMiddleware.ts @@ -23,7 +23,7 @@ type CacheMiddlewareOptions = { logger: LoggerService; }; -type ErrorCallback = (err?: Error) => void; +type ErrorCallback = (err?: Error | null) => void; export const createCacheMiddleware = ({ cache, diff --git a/plugins/techdocs-module-addons-contrib/CHANGELOG.md b/plugins/techdocs-module-addons-contrib/CHANGELOG.md index df1d97d439..96d9129b4d 100644 --- a/plugins/techdocs-module-addons-contrib/CHANGELOG.md +++ b/plugins/techdocs-module-addons-contrib/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-techdocs-module-addons-contrib +## 1.1.31 + +### Patch Changes + +- 8d6709e: Updated tests to match test-utils change +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-techdocs-react@1.3.6 + +## 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 + +## 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 + ## 1.1.30 ### Patch Changes diff --git a/plugins/techdocs-module-addons-contrib/package.json b/plugins/techdocs-module-addons-contrib/package.json index f6578bdf6e..68fd2ede1a 100644 --- a/plugins/techdocs-module-addons-contrib/package.json +++ b/plugins/techdocs-module-addons-contrib/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-module-addons-contrib", - "version": "1.1.30", + "version": "1.1.31", "description": "Plugin module for contributed TechDocs Addons", "backstage": { "role": "frontend-plugin-module", @@ -75,7 +75,8 @@ "@types/react": "^18.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.3.0", + "shadow-dom-testing-library": "^1.13.1" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", diff --git a/plugins/techdocs-module-addons-contrib/src/ExpandableNavigation/ExpandableNavigation.test.tsx b/plugins/techdocs-module-addons-contrib/src/ExpandableNavigation/ExpandableNavigation.test.tsx index 90a3fa7378..c4d07df7ee 100644 --- a/plugins/techdocs-module-addons-contrib/src/ExpandableNavigation/ExpandableNavigation.test.tsx +++ b/plugins/techdocs-module-addons-contrib/src/ExpandableNavigation/ExpandableNavigation.test.tsx @@ -17,6 +17,7 @@ import { TechDocsAddonTester } from '@backstage/plugin-techdocs-addons-test-utils'; import { fireEvent, waitFor } from '@testing-library/react'; +import { screen } from 'shadow-dom-testing-library'; import { ExpandableNavigation } from '../plugin'; import { entityPresentationApiRef } from '@backstage/plugin-catalog-react'; @@ -94,25 +95,24 @@ describe('ExpandableNavigation', () => { }); it('renders without exploding', async () => { - const { getByRole } = await TechDocsAddonTester.buildAddonsInTechDocs([ + await TechDocsAddonTester.buildAddonsInTechDocs([]) + .withDom(mockNavWithSublevels) + .withApis([[entityPresentationApiRef, entityPresentationApiMock]]) + .renderWithEffects(); + + expect( + screen.getByShadowRole('button', { name: 'expand-nav' }), + ).toBeInTheDocument(); + }); + + it('expands and collapses navigation', async () => { + const { shadowRoot } = await TechDocsAddonTester.buildAddonsInTechDocs([ , ]) .withDom(mockNavWithSublevels) .withApis([[entityPresentationApiRef, entityPresentationApiMock]]) .renderWithEffects(); - expect(getByRole('button', { name: 'expand-nav' })).toBeInTheDocument(); - }); - - it('expands and collapses navigation', async () => { - const { getByRole, shadowRoot } = - await TechDocsAddonTester.buildAddonsInTechDocs([ - , - ]) - .withDom(mockNavWithSublevels) - .withApis([[entityPresentationApiRef, entityPresentationApiMock]]) - .renderWithEffects(); - const toggles = shadowRoot!.querySelectorAll('.md-toggle'); @@ -121,18 +121,24 @@ describe('ExpandableNavigation', () => { expect(item).not.toBeChecked(); }); - const expandButton = getByRole('button', { name: 'expand-nav' }); + const expandButton = screen.getByShadowRole('button', { + name: 'expand-nav', + }); fireEvent.click(expandButton); await waitFor(() => { - expect(getByRole('button', { name: 'collapse-nav' })).toBeInTheDocument(); + expect( + screen.getByShadowRole('button', { name: 'collapse-nav' }), + ).toBeInTheDocument(); toggles.forEach(item => { expect(item).toBeChecked(); }); }); - const collapseButton = getByRole('button', { name: 'collapse-nav' }); + const collapseButton = screen.getByShadowRole('button', { + name: 'collapse-nav', + }); fireEvent.click(collapseButton); @@ -144,15 +150,13 @@ describe('ExpandableNavigation', () => { }); it('does not render when navigation has no sublevels', async () => { - const { queryByRole } = await TechDocsAddonTester.buildAddonsInTechDocs([ - , - ]) + await TechDocsAddonTester.buildAddonsInTechDocs([]) .withDom(mockNavWithoutSublevels) .withApis([[entityPresentationApiRef, entityPresentationApiMock]]) .renderWithEffects(); expect( - queryByRole('button', { name: 'expand-nav' }), + screen.queryByShadowRole('button', { name: 'expand-nav' }), ).not.toBeInTheDocument(); }); }); diff --git a/plugins/techdocs-module-addons-contrib/src/LightBox/LightBox.test.tsx b/plugins/techdocs-module-addons-contrib/src/LightBox/LightBox.test.tsx index 7171629d89..0f618013f1 100644 --- a/plugins/techdocs-module-addons-contrib/src/LightBox/LightBox.test.tsx +++ b/plugins/techdocs-module-addons-contrib/src/LightBox/LightBox.test.tsx @@ -15,6 +15,7 @@ */ import { TechDocsAddonTester } from '@backstage/plugin-techdocs-addons-test-utils'; +import { screen } from 'shadow-dom-testing-library'; import { LightBox } from '../plugin'; import { entityPresentationApiRef } from '@backstage/plugin-catalog-react'; @@ -30,20 +31,16 @@ describe('LightBox', () => { }); it('renders without exploding', async () => { - const { getByText } = await TechDocsAddonTester.buildAddonsInTechDocs([ - , - ]) + await TechDocsAddonTester.buildAddonsInTechDocs([]) .withDom(TEST_CONTENT) .withApis([[entityPresentationApiRef, entityPresentationApiMock]]) .renderWithEffects(); - expect(getByText('TEST_CONTENT')).toBeInTheDocument(); + expect(screen.getByShadowText('TEST_CONTENT')).toBeInTheDocument(); }); it('Add onclick event to images', async () => { - const { getByTestId } = await TechDocsAddonTester.buildAddonsInTechDocs([ - , - ]) + await TechDocsAddonTester.buildAddonsInTechDocs([]) .withDom( { .withApis([[entityPresentationApiRef, entityPresentationApiMock]]) .renderWithEffects(); - expect(getByTestId('fixture').onclick).not.toBeUndefined(); - expect(getByTestId('fixture').onclick).toEqual(expect.any(Function)); + expect(screen.getByShadowTestId('fixture').onclick).not.toBeUndefined(); + expect(screen.getByShadowTestId('fixture').onclick).toEqual( + expect.any(Function), + ); }); }); diff --git a/plugins/techdocs-module-addons-contrib/src/ReportIssue/ReportIssue.test.tsx b/plugins/techdocs-module-addons-contrib/src/ReportIssue/ReportIssue.test.tsx index 1ee9e87e05..153c1c603f 100644 --- a/plugins/techdocs-module-addons-contrib/src/ReportIssue/ReportIssue.test.tsx +++ b/plugins/techdocs-module-addons-contrib/src/ReportIssue/ReportIssue.test.tsx @@ -17,6 +17,7 @@ import { TechDocsAddonTester } from '@backstage/plugin-techdocs-addons-test-utils'; import { fireEvent, waitFor } from '@testing-library/react'; +import { screen } from 'shadow-dom-testing-library'; import { scmIntegrationsApiRef } from '@backstage/integration-react'; import { ReportIssue } from '../plugin'; @@ -67,58 +68,57 @@ describe('ReportIssue', () => { it('renders github link without exploding', async () => { byUrl.mockReturnValue({ type: 'github' }); - const { shadowRoot, getByText } = - await TechDocsAddonTester.buildAddonsInTechDocs([ - , - ]) - .withDom( - - - -
-
-
-
+ const { shadowRoot } = await TechDocsAddonTester.buildAddonsInTechDocs([ + , + ]) + .withDom( + + + +
+
+
+
-
- + - - , - ) - .withApis([ - [scmIntegrationsApiRef, { byUrl }], - [entityPresentationApiRef, entityPresentationApiMock], - ]) - .renderWithEffects(); +
+ + , + ) + .withApis([ + [scmIntegrationsApiRef, { byUrl }], + [entityPresentationApiRef, entityPresentationApiMock], + ]) + .renderWithEffects(); (shadowRoot as ShadowRoot & Pick).getSelection = () => selection; await waitFor(() => { - expect(getByText('Edit page')).toBeInTheDocument(); + expect(screen.getByShadowText('Edit page')).toBeInTheDocument(); }); fireSelectionChangeEvent(window); await waitFor(() => { - const link = getByText('Open new Github issue'); + const link = screen.getByShadowText('Open new Github issue'); expect(link).toHaveAttribute( 'href', 'https://github.com/backstage/backstage/issues/new?title=Documentation%20feedback%3A%20his%20&body=%23%23%20Documentation%20Feedback%20%F0%9F%93%9D%0A%0A%20%23%23%23%23%20The%20highlighted%20text%3A%20%0A%0A%20%3E%20his%0A%0A%20%23%23%23%23%20The%20comment%20on%20the%20text%3A%20%0A%20_%3Ereplace%20this%20line%20with%20your%20comment%3C_%0A%0A%20___%0ABackstage%20URL%3A%20%3Chttp%3A%2F%2Flocalhost%2F%3E%20%0AMarkdown%20URL%3A%20%3Chttps%3A%2F%2Fgithub.com%2Fbackstage%2Fbackstage%2Fblob%2Fmaster%2Fdocs%2FREADME.md%3E', @@ -128,60 +128,61 @@ describe('ReportIssue', () => { it('renders gitlab link without exploding', async () => { byUrl.mockReturnValue({ type: 'gitlab' }); - const { shadowRoot, getByText, queryByTestId } = - await TechDocsAddonTester.buildAddonsInTechDocs([ - , - ]) - .withDom( - - - -
-
-
-
+ const { shadowRoot } = await TechDocsAddonTester.buildAddonsInTechDocs([ + , + ]) + .withDom( + + + +
+
+
+
-
- + - - , - ) - .withApis([ - [scmIntegrationsApiRef, { byUrl }], - [entityPresentationApiRef, entityPresentationApiMock], - ]) - .renderWithEffects(); +
+ + , + ) + .withApis([ + [scmIntegrationsApiRef, { byUrl }], + [entityPresentationApiRef, entityPresentationApiMock], + ]) + .renderWithEffects(); (shadowRoot as ShadowRoot & Pick).getSelection = () => selection; await waitFor(() => { - expect(getByText('Edit page')).toBeInTheDocument(); + expect(screen.getByShadowText('Edit page')).toBeInTheDocument(); }); fireSelectionChangeEvent(window); await waitFor(() => { - expect(queryByTestId('report-issue-addon')).toBeInTheDocument(); + expect( + screen.getByShadowTestId('report-issue-addon'), + ).toBeInTheDocument(); - const link = getByText('Open new Gitlab issue'); + const link = screen.getByShadowText('Open new Gitlab issue'); expect(link).toHaveAttribute( 'href', 'https://gitlab.com/backstage/backstage/issues/new?issue[title]=Documentation%20feedback%3A%20his%20&issue[description]=%23%23%20Documentation%20Feedback%20%F0%9F%93%9D%0A%0A%20%23%23%23%23%20The%20highlighted%20text%3A%20%0A%0A%20%3E%20his%0A%0A%20%23%23%23%23%20The%20comment%20on%20the%20text%3A%20%0A%20_%3Ereplace%20this%20line%20with%20your%20comment%3C_%0A%0A%20___%0ABackstage%20URL%3A%20%3Chttp%3A%2F%2Flocalhost%2F%3E%20%0AMarkdown%20URL%3A%20%3Chttps%3A%2F%2Fgitlab.com%2Fbackstage%2Fbackstage%2F-%2Fblob%2Fmaster%2Fdocs%2FREADME.md%3E', @@ -197,60 +198,61 @@ describe('ReportIssue', () => { body: options.selection.toString().trim(), }); - const { shadowRoot, getByText, queryByTestId } = - await TechDocsAddonTester.buildAddonsInTechDocs([ - , - ]) - .withDom( - - - -
-
-
-
+ const { shadowRoot } = await TechDocsAddonTester.buildAddonsInTechDocs([ + , + ]) + .withDom( + + + +
+
+
+
-
- + - - , - ) - .withApis([ - [scmIntegrationsApiRef, { byUrl }], - [entityPresentationApiRef, entityPresentationApiMock], - ]) - .renderWithEffects(); +
+ + , + ) + .withApis([ + [scmIntegrationsApiRef, { byUrl }], + [entityPresentationApiRef, entityPresentationApiMock], + ]) + .renderWithEffects(); (shadowRoot as ShadowRoot & Pick).getSelection = () => selection; await waitFor(() => { - expect(getByText('Edit page')).toBeInTheDocument(); + expect(screen.getByShadowText('Edit page')).toBeInTheDocument(); }); fireSelectionChangeEvent(window); await waitFor(() => { - expect(queryByTestId('report-issue-addon')).toBeInTheDocument(); + expect( + screen.getByShadowTestId('report-issue-addon'), + ).toBeInTheDocument(); - const link = getByText('Open new Gitlab issue'); + const link = screen.getByShadowText('Open new Gitlab issue'); expect(link).toHaveAttribute( 'href', 'https://gitlab.com/backstage/backstage/issues/new?issue[title]=Custom&issue[description]=his', @@ -261,45 +263,46 @@ describe('ReportIssue', () => { it('does not render report issue link for unsupported repository type', async () => { byUrl.mockReturnValue({ type: 'gerrit', resource: 'gerrit.example.com' }); - const { shadowRoot, getByText, queryByTestId } = - await TechDocsAddonTester.buildAddonsInTechDocs([ - , - ]) - .withDom( - - - -
-
- + const { shadowRoot } = await TechDocsAddonTester.buildAddonsInTechDocs([ + , + ]) + .withDom( + + + +
+ - - , - ) - .withApis([[scmIntegrationsApiRef, { byUrl }]]) - .renderWithEffects(); +
+ + , + ) + .withApis([[scmIntegrationsApiRef, { byUrl }]]) + .renderWithEffects(); (shadowRoot as ShadowRoot & Pick).getSelection = () => selection; await waitFor(() => { - expect(getByText('Edit page')).toBeInTheDocument(); + expect(screen.getByShadowText('Edit page')).toBeInTheDocument(); }); fireSelectionChangeEvent(window); await waitFor(() => { - expect(queryByTestId('report-issue-addon')).not.toBeInTheDocument(); + expect( + screen.queryByShadowTestId('report-issue-addon'), + ).not.toBeInTheDocument(); }); }); }); diff --git a/plugins/techdocs-module-addons-contrib/src/TextSize/TextSize.test.tsx b/plugins/techdocs-module-addons-contrib/src/TextSize/TextSize.test.tsx index 92cab255a7..03571c1cd1 100644 --- a/plugins/techdocs-module-addons-contrib/src/TextSize/TextSize.test.tsx +++ b/plugins/techdocs-module-addons-contrib/src/TextSize/TextSize.test.tsx @@ -16,6 +16,7 @@ import { TechDocsAddonTester } from '@backstage/plugin-techdocs-addons-test-utils'; import { act, fireEvent, waitFor } from '@testing-library/react'; +import { screen } from 'shadow-dom-testing-library'; import { TextSize } from '../plugin'; import { useShadowRootElements } from '@backstage/plugin-techdocs-react'; import { entityPresentationApiRef } from '@backstage/plugin-catalog-react'; @@ -42,33 +43,30 @@ describe('TextSize', () => { }); it('renders without exploding', async () => { - const { getByText } = await TechDocsAddonTester.buildAddonsInTechDocs([ - , - ]) + await TechDocsAddonTester.buildAddonsInTechDocs([]) .withDom(TEST_CONTENT) .withApis([[entityPresentationApiRef, entityPresentationApiMock]]) .renderWithEffects(); - expect(getByText('TEST_CONTENT')).toBeInTheDocument(); + expect(screen.getByShadowText('TEST_CONTENT')).toBeInTheDocument(); }); it('changes content text size using slider', async () => { - const { getByTitle, getByText, getByRole, getByDisplayValue } = - await TechDocsAddonTester.buildAddonsInTechDocs([]) - .withDom(TEST_CONTENT) - .withApis([[entityPresentationApiRef, entityPresentationApiMock]]) - .renderWithEffects(); + await TechDocsAddonTester.buildAddonsInTechDocs([]) + .withDom(TEST_CONTENT) + .withApis([[entityPresentationApiRef, entityPresentationApiMock]]) + .renderWithEffects(); - const content = getByText('TEST_CONTENT'); + const content = screen.getByShadowText('TEST_CONTENT'); useShadowRootElementsMock.mockReturnValue([content]); - fireEvent.click(getByTitle('Settings')); + fireEvent.click(screen.getByShadowTitle('Settings')); await waitFor(() => { - expect(getByText('Text size')).toBeInTheDocument(); + expect(screen.getByShadowText('Text size')).toBeInTheDocument(); }); - const slider = getByRole('slider'); + const slider = screen.getByShadowRole('slider'); act(() => { slider.focus(); @@ -79,12 +77,12 @@ describe('TextSize', () => { }); await waitFor(() => { - expect(getByDisplayValue('115')).toBeInTheDocument(); + expect(screen.getByShadowDisplayValue('115')).toBeInTheDocument(); }); expect(slider).toHaveTextContent('115%'); - let style = window.getComputedStyle(getByText('TEST_CONTENT')); + let style = window.getComputedStyle(screen.getByShadowText('TEST_CONTENT')); await waitFor(() => { expect(style.getPropertyValue('--md-typeset-font-size')).toBe('18.4px'); @@ -95,60 +93,54 @@ describe('TextSize', () => { }); await waitFor(() => { - expect(getByDisplayValue('100')).toBeInTheDocument(); + expect(screen.getByShadowDisplayValue('100')).toBeInTheDocument(); }); expect(slider).toHaveTextContent('100%'); - style = window.getComputedStyle(getByText('TEST_CONTENT')); + style = window.getComputedStyle(screen.getByShadowText('TEST_CONTENT')); expect(style.getPropertyValue('--md-typeset-font-size')).toBe('16px'); }); it('changes content text size using buttons', async () => { - const { - getByTitle, - getByText, - getByRole, - getByLabelText, - getByDisplayValue, - } = await TechDocsAddonTester.buildAddonsInTechDocs([]) + await TechDocsAddonTester.buildAddonsInTechDocs([]) .withDom(TEST_CONTENT) .withApis([[entityPresentationApiRef, entityPresentationApiMock]]) .renderWithEffects(); - const content = getByText('TEST_CONTENT'); + const content = screen.getByShadowText('TEST_CONTENT'); useShadowRootElementsMock.mockReturnValue([content]); - fireEvent.click(getByTitle('Settings')); + fireEvent.click(screen.getByShadowTitle('Settings')); await waitFor(() => { - expect(getByText('Text size')).toBeInTheDocument(); + expect(screen.getByShadowText('Text size')).toBeInTheDocument(); }); - fireEvent.click(getByLabelText('Increase text size')); + fireEvent.click(screen.getByShadowLabelText('Increase text size')); await waitFor(() => { - expect(getByDisplayValue('115')).toBeInTheDocument(); + expect(screen.getByShadowDisplayValue('115')).toBeInTheDocument(); }); - const slider = getByRole('slider'); + const slider = screen.getByShadowRole('slider'); expect(slider).toHaveTextContent('115%'); - let style = window.getComputedStyle(getByText('TEST_CONTENT')); + let style = window.getComputedStyle(screen.getByShadowText('TEST_CONTENT')); expect(style.getPropertyValue('--md-typeset-font-size')).toBe('18.4px'); - fireEvent.click(getByLabelText('Decrease text size')); + fireEvent.click(screen.getByShadowLabelText('Decrease text size')); await waitFor(() => { - expect(getByDisplayValue('100')).toBeInTheDocument(); + expect(screen.getByShadowDisplayValue('100')).toBeInTheDocument(); }); expect(slider).toHaveTextContent('100%'); - style = window.getComputedStyle(getByText('TEST_CONTENT')); + style = window.getComputedStyle(screen.getByShadowText('TEST_CONTENT')); expect(style.getPropertyValue('--md-typeset-font-size')).toBe('16px'); }); diff --git a/plugins/techdocs-node/CHANGELOG.md b/plugins/techdocs-node/CHANGELOG.md index 5ea1dca965..94c0a6e912 100644 --- a/plugins/techdocs-node/CHANGELOG.md +++ b/plugins/techdocs-node/CHANGELOG.md @@ -1,5 +1,45 @@ # @backstage/plugin-techdocs-node +## 1.13.10 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 703f8c0: There was an issue in the uploading of large size files to the AWS S3. We have modified the logic by adding retry along with multipart uploading functionality. +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/backend-plugin-api@1.6.0 + +## 1.13.10-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- 703f8c0: There was an issue in the uploading of large size files to the AWS S3. We have modified the logic by adding retry along with multipart uploading functionality. +- Updated dependencies + - @backstage/integration@1.18.3-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.19 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-techdocs-common@0.1.1 + +## 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 + ## 1.13.9 ### Patch Changes diff --git a/plugins/techdocs-node/package.json b/plugins/techdocs-node/package.json index 7a063d6fdb..c5858cdf8f 100644 --- a/plugins/techdocs-node/package.json +++ b/plugins/techdocs-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-node", - "version": "1.13.9", + "version": "1.13.10", "description": "Common node.js functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli", "backstage": { "role": "node-library", @@ -66,7 +66,7 @@ "@trendyol-js/openstack-swift-sdk": "^0.0.7", "@types/express": "^4.17.6", "dockerode": "^4.0.0", - "express": "^4.17.1", + "express": "^4.22.0", "fs-extra": "^11.2.0", "git-url-parse": "^15.0.0", "hpagent": "^1.2.0", diff --git a/plugins/techdocs-node/src/stages/publish/awsS3.test.ts b/plugins/techdocs-node/src/stages/publish/awsS3.test.ts index 6fb855d199..8be8c05994 100644 --- a/plugins/techdocs-node/src/stages/publish/awsS3.test.ts +++ b/plugins/techdocs-node/src/stages/publish/awsS3.test.ts @@ -22,6 +22,7 @@ import { ListObjectsV2Command, PutObjectCommand, S3Client, + S3ServiceException, UploadPartCommand, } from '@aws-sdk/client-s3'; import { Entity, DEFAULT_NAMESPACE } from '@backstage/catalog-model'; @@ -43,10 +44,13 @@ import { mockServices, } from '@backstage/backend-test-utils'; +jest.setTimeout(30_000); + const env = process.env; let s3Mock: AwsClientStub; -const mockDir = createMockDirectory(); +// Create a new MockDirectory for each test to avoid Windows file locking issues +let mockDir: ReturnType; function getMockCredentialProvider(): Promise { return Promise.resolve({ @@ -155,7 +159,7 @@ describe('AwsS3Publish', () => { build_timestamp: 612741599, }; - const directory = getEntityRootDir(entity); + let directory: string; const files = { 'index.html': '', @@ -176,7 +180,7 @@ describe('AwsS3Publish', () => { }, }; - beforeEach(() => { + beforeEach(async () => { process.env = { ...env }; process.env.AWS_REGION = 'us-west-2'; @@ -185,6 +189,12 @@ describe('AwsS3Publish', () => { getMockCredentialProvider(), ); + // Create a fresh mockdirectory for each test to avoid windows file locking + mockDir = createMockDirectory(); + // Calculate directory path with the new mockDir instance + directory = getEntityRootDir(entity); + + // Set up the test files mockDir.setContent({ [directory]: files, }); @@ -292,6 +302,161 @@ describe('AwsS3Publish', () => { }); }); + describe('retry mechanism', () => { + it('should retry with custom retry strategy', async () => { + const publisher = await createPublisherFromConfig(); + const customRetryStrategy = jest.fn(error => { + return error.name === 'NetworkingError'; + }); + + s3Mock + .on(ListObjectsV2Command) + .rejectsOnce( + new S3ServiceException({ + name: 'NetworkingError', + $fault: 'client', + $metadata: {}, + }), + ) + .resolvesOnce({ Contents: [] }); + + await (publisher as AwsS3Publish).retryOperation( + async () => { + const command = new ListObjectsV2Command({ Bucket: 'bucketName' }); + return (publisher as AwsS3Publish).storageClient.send(command); + }, + 'TestOperation', + 3, + customRetryStrategy, + ); + + expect(customRetryStrategy).toHaveBeenCalled(); + }); + + it('should use default retry strategy when no custom strategy provided', async () => { + const publisher = await createPublisherFromConfig(); + s3Mock + .on(ListObjectsV2Command) + .rejectsOnce( + new S3ServiceException({ + name: 'RequestTimeout', + $fault: 'client', + $metadata: {}, + }), + ) + .resolvesOnce({ Contents: [] }); + + await (publisher as AwsS3Publish).retryOperation( + async () => { + const command = new ListObjectsV2Command({ Bucket: 'bucketName' }); + return (publisher as AwsS3Publish).storageClient.send(command); + }, + 'TestOperation', + 3, + ); + }); + + it('should retry on server errors (5xx)', async () => { + const publisher = await createPublisherFromConfig(); + s3Mock + .on(ListObjectsV2Command) + .rejectsOnce( + new S3ServiceException({ + name: 'InternalError', + $fault: 'server', + $metadata: { httpStatusCode: 500 }, + }), + ) + .resolvesOnce({ Contents: [] }); + + await (publisher as AwsS3Publish).retryOperation( + async () => { + const command = new ListObjectsV2Command({ Bucket: 'bucketName' }); + return (publisher as AwsS3Publish).storageClient.send(command); + }, + 'TestOperation', + 3, + ); + }); + + it('should retry on specific 4xx errors that are transient', async () => { + const publisher = await createPublisherFromConfig(); + s3Mock + .on(ListObjectsV2Command) + .rejectsOnce( + new S3ServiceException({ + name: 'RequestTimeout', + $fault: 'client', + $metadata: { httpStatusCode: 408 }, + }), + ) + .resolvesOnce({ Contents: [] }); + + await (publisher as AwsS3Publish).retryOperation( + async () => { + const command = new ListObjectsV2Command({ Bucket: 'bucketName' }); + return (publisher as AwsS3Publish).storageClient.send(command); + }, + 'TestOperation', + 3, + ); + }); + + it('should use exact error code matching for transient errors', async () => { + const publisher = await createPublisherFromConfig(); + // Test that ConnectionError (exact match) is retried, but ConnectionErrorSomething (substring) is not + s3Mock + .on(ListObjectsV2Command) + .rejectsOnce( + new S3ServiceException({ + name: 'ConnectionError', + $fault: 'client', + $metadata: {}, + }), + ) + .resolvesOnce({ Contents: [] }); + + await (publisher as AwsS3Publish).retryOperation( + async () => { + const command = new ListObjectsV2Command({ Bucket: 'bucketName' }); + return (publisher as AwsS3Publish).storageClient.send(command); + }, + 'TestOperation', + 3, + ); + }); + + it('should apply exponential backoff with correct calculation', async () => { + const publisher = await createPublisherFromConfig(); + const startTime = Date.now(); + + s3Mock + .on(ListObjectsV2Command) + .rejectsOnce( + new S3ServiceException({ + name: 'SlowDown', + $fault: 'server', + $metadata: {}, + }), + ) + .resolvesOnce({ Contents: [] }); + + await (publisher as AwsS3Publish).retryOperation( + async () => { + const command = new ListObjectsV2Command({ Bucket: 'bucketName' }); + return (publisher as AwsS3Publish).storageClient.send(command); + }, + 'TestOperation', + 3, + ); + + const elapsedTime = Date.now() - startTime; + // First attempt fails, then backoff with baseDelay * 2^(attempt-1) = 1000 * 2^0 = 1000ms minimum + // Adding jitter (0-1000ms), so we expect at least 1000ms total + expect(elapsedTime).toBeGreaterThanOrEqual(900); + }); + }); + describe('getReadiness', () => { it('should validate correct config', async () => { const publisher = await createPublisherFromConfig(); @@ -405,7 +570,7 @@ describe('AwsS3Publish', () => { bucketName: bucketName, }); await publisher.publish({ entity, directory }); - expect(loggerInfoSpy).toHaveBeenLastCalledWith( + expect(loggerInfoSpy).toHaveBeenCalledWith( `Successfully deleted stale files for Entity ${entity.metadata.name}. Total number of files: 1`, ); }); @@ -549,7 +714,7 @@ describe('AwsS3Publish', () => { }); it('should return an error if the techdocs_metadata.json file cannot be read from stream', async () => { - s3Mock.on(GetObjectCommand).callsFake(_ => { + s3Mock.on(GetObjectCommand).callsFake(() => { return { Body: new ErrorReadable('No stream!'), }; @@ -688,7 +853,7 @@ describe('AwsS3Publish', () => { }); it('should return 404 if file cannot be read from stream', async () => { - s3Mock.on(GetObjectCommand).callsFake(_ => { + s3Mock.on(GetObjectCommand).callsFake(() => { return { Body: new ErrorReadable('No stream!'), }; diff --git a/plugins/techdocs-node/src/stages/publish/awsS3.ts b/plugins/techdocs-node/src/stages/publish/awsS3.ts index bd8b96ee14..157c2ffdeb 100644 --- a/plugins/techdocs-node/src/stages/publish/awsS3.ts +++ b/plugins/techdocs-node/src/stages/publish/awsS3.ts @@ -16,6 +16,10 @@ import { Entity, CompoundEntityRef } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { assertError, ForwardedError } from '@backstage/errors'; + +// Maximum size in bytes for a single upload part (5MB) +const MAX_SINGLE_UPLOAD_BYTES = 5 * 1024 * 1024; + import { AwsCredentialsManager, DefaultAwsCredentialsManager, @@ -26,10 +30,12 @@ import { DeleteObjectCommand, HeadBucketCommand, HeadObjectCommand, + PutObjectCommand, PutObjectCommandInput, ListObjectsV2CommandOutput, ListObjectsV2Command, S3Client, + S3ServiceException, } from '@aws-sdk/client-s3'; import { fromTemporaryCredentials } from '@aws-sdk/credential-providers'; import { NodeHttpHandler } from '@smithy/node-http-handler'; @@ -78,12 +84,13 @@ const streamToBuffer = (stream: Readable): Promise => { }; export class AwsS3Publish implements PublisherBase { - private readonly storageClient: S3Client; + public readonly storageClient: S3Client; private readonly bucketName: string; private readonly legacyPathCasing: boolean; private readonly logger: LoggerService; private readonly bucketRootPath: string; private readonly sse?: 'aws:kms' | 'AES256'; + private readonly maxAttempts: number; constructor(options: { storageClient: S3Client; @@ -92,6 +99,7 @@ export class AwsS3Publish implements PublisherBase { logger: LoggerService; bucketRootPath: string; sse?: 'aws:kms' | 'AES256'; + maxAttempts: number; }) { this.storageClient = options.storageClient; this.bucketName = options.bucketName; @@ -99,6 +107,7 @@ export class AwsS3Publish implements PublisherBase { this.logger = options.logger; this.bucketRootPath = options.bucketRootPath; this.sse = options.sse; + this.maxAttempts = options.maxAttempts; } static async fromConfig( @@ -164,7 +173,7 @@ export class AwsS3Publish implements PublisherBase { 'techdocs.publisher.awsS3.s3ForcePathStyle', ); - // AWS MAX ATTEMPTS is an optional config. If missing, default value of 3 is used + // AWS MAX ATTEMPTS is an optional config. If missing, default value of 5 is used const maxAttempts = config.getOptionalNumber( 'techdocs.publisher.awsS3.maxAttempts', ); @@ -175,11 +184,16 @@ export class AwsS3Publish implements PublisherBase { ...(region && { region }), ...(endpoint && { endpoint }), ...(forcePathStyle && { forcePathStyle }), - ...(maxAttempts && { maxAttempts }), - ...(httpsProxy && { - requestHandler: new NodeHttpHandler({ + // Enhanced retry configuration for better reliability + maxAttempts: maxAttempts || 5, + retryMode: 'adaptive', + // Enhanced connection settings for large file uploads + requestHandler: new NodeHttpHandler({ + ...(httpsProxy && { httpsAgent: new HttpsProxyAgent({ proxy: httpsProxy }), }), + connectionTimeout: 60000, + socketTimeout: 120000, }), }); @@ -195,6 +209,7 @@ export class AwsS3Publish implements PublisherBase { legacyPathCasing, logger, sse, + maxAttempts: maxAttempts || 5, }); } @@ -250,6 +265,89 @@ export class AwsS3Publish implements PublisherBase { return explicitCredentials; } + /** + * Custom retry wrapper for S3 operations with detailed error handling. + */ + public async retryOperation( + operation: () => Promise, + operationName: string, + maxAttempts: number = 3, + shouldRetry: ( + error: S3ServiceException, + ) => boolean = this.defaultShouldRetry.bind(this), + ): Promise { + for (let attempt = 1; attempt < maxAttempts; attempt++) { + try { + return await operation(); + } catch (error) { + const e = error as S3ServiceException; + if (!shouldRetry(e)) { + this.logger.error(`${operationName} failed: ${e.message}`); + throw e; + } + + this.logger.warn(`${operationName} failed, retrying...`, { + attempt, + maxAttempts, + error: e.message, + errorCode: e.name, + httpStatusCode: e.$metadata?.httpStatusCode, + }); + + // Enhanced exponential backoff with jitter + const baseDelay = operationName.startsWith('Upload-') ? 2000 : 1000; + const backoffDelay = Math.min( + baseDelay * Math.pow(2, attempt - 1), + 30000, + ); + const jitter = Math.random() * 1000; + await new Promise(resolve => + setTimeout(resolve, backoffDelay + jitter), + ); + } + } + return await operation(); + } + + /** + * Determines if an S3 operation should be retried based on the error details. + */ + private defaultShouldRetry(error: S3ServiceException): boolean { + const httpStatusCode = error.$metadata?.httpStatusCode; + const errorCode = error.name; + + // Truly transient errors that should always be retried + const transientErrors = [ + 'NetworkingError', + 'TimeoutError', + 'ConnectionError', + 'RequestTimeout', + 'ServiceUnavailable', + 'SlowDown', + 'ThrottlingException', + ]; + + // Server errors are always considered transient + if (httpStatusCode && httpStatusCode >= 500) { + return true; + } + + // Specific 4xx errors that are known to be transient + if (httpStatusCode && httpStatusCode >= 400 && httpStatusCode < 500) { + const retriable4xxErrors = [ + 'RequestTimeout', + 'RequestTimeoutException', + 'PriorRequestNotComplete', + ]; + return retriable4xxErrors.includes(errorCode); + } + + // Check against known transient errors + return transientErrors.some( + retriableError => + errorCode === retriableError || error.message.includes(retriableError), + ); + } /** * Check if the defined bucket exists. Being able to connect means the configuration is good @@ -273,13 +371,15 @@ export class AwsS3Publish implements PublisherBase { 'explicitly defining credentials and region in techdocs.publisher.awsS3 in app config or ' + 'by using environment variables. Refer to https://backstage.io/docs/features/techdocs/using-cloud-storage', ); - this.logger.error(`from AWS client library`, error); + this.logger.error( + `from AWS client library`, + error instanceof Error ? error : new Error(String(error)), + ); return { isAvailable: false, }; } } - /** * Upload all the files from the generated `directory` to the S3 bucket. * Directory structure used in the bucket is - entityNamespace/entityKind/entityName/index.html @@ -293,6 +393,9 @@ export class AwsS3Publish implements PublisherBase { const bucketRootPath = this.bucketRootPath; const sse = this.sse; + // Track timing for performance monitoring + const publishStartTime = Date.now(); + // First, try to retrieve a list of all individual files currently existing let existingFiles: string[] = []; try { @@ -302,9 +405,20 @@ export class AwsS3Publish implements PublisherBase { useLegacyPathCasing, bucketRootPath, ); - existingFiles = await this.getAllObjectsFromBucket({ - prefix: remoteFolder, - }); + const response = await this.retryOperation( + async () => { + const listCommand = new ListObjectsV2Command({ + Bucket: this.bucketName, + Prefix: remoteFolder, + }); + return this.storageClient.send(listCommand); + }, + 'ListObjects', + this.maxAttempts, + ); + existingFiles = (response.Contents || []) + .map(f => f.Key || '') + .filter(f => !!f); } catch (e) { assertError(e); this.logger.error( @@ -323,27 +437,99 @@ export class AwsS3Publish implements PublisherBase { await bulkStorageOperation( async absoluteFilePath => { const relativeFilePath = path.relative(directory, absoluteFilePath); - const fileStream = fs.createReadStream(absoluteFilePath); - + const s3Key = getCloudPathForLocalPath( + entity, + relativeFilePath, + useLegacyPathCasing, + bucketRootPath, + ); + // Create params without the Body because the body must be the + // actual file contents (Buffer or Readable), not the path string. + // For multipart uploads we attach a Readable stream to avoid + // buffering large files in memory. For simple uploads we attach + // a Buffer read from disk. const params: PutObjectCommandInput = { Bucket: this.bucketName, - Key: getCloudPathForLocalPath( - entity, - relativeFilePath, - useLegacyPathCasing, - bucketRootPath, - ), - Body: fileStream, + Key: s3Key, ...(sse && { ServerSideEncryption: sse }), }; objects.push(params.Key!); + // Get file stats before upload + const stats = await fs.stat(absoluteFilePath); + const fileSizeInBytes = stats.size; - const upload = new Upload({ - client: this.storageClient, - params, - }); - return upload.done(); + // Check if this is a large file that requires multipart upload + if (fileSizeInBytes >= MAX_SINGLE_UPLOAD_BYTES) { + // Try multipart upload for large files + try { + // Create stream and Upload inside retry closure so stream is + // recreated on each retry attempt (streams are consumable once). + await this.retryOperation( + () => { + // Create a fresh stream on each attempt + const fileStream = fs.createReadStream(absoluteFilePath); + const uploadParams = { ...params, Body: fileStream }; + + const upload = new Upload({ + client: this.storageClient, + params: uploadParams, + partSize: MAX_SINGLE_UPLOAD_BYTES, + queueSize: 3, + leavePartsOnError: false, + }); + return upload.done(); + }, + `Upload-${params.Key}`, + this.maxAttempts, + ); + return; + } catch (multipartError) { + const s3Error = multipartError as any; + const errorName = s3Error?.name || 'Unknown'; + + // For specific multipart errors, attempt simple upload fallback + if (errorName === 'InvalidPart' || errorName === 'NoSuchUpload') { + this.logger.warn( + `Multipart upload failed for ${params.Key}, attempting simple upload fallback.`, + ); + } else { + // Non-recoverable multipart error, throw it + this.logger.error( + `Multipart upload failed for ${params.Key}: ${ + multipartError instanceof Error + ? multipartError.message + : String(multipartError) + }`, + ); + throw multipartError; + } + } + } + + // Use simple upload for small files or as fallback from multipart + try { + const fileContent = await fs.readFile(absoluteFilePath); + const putParams = { ...params, Body: fileContent }; + await this.retryOperation( + () => this.storageClient.send(new PutObjectCommand(putParams)), + `Upload-${params.Key}`, + this.maxAttempts, + ); + + if (fileSizeInBytes >= MAX_SINGLE_UPLOAD_BYTES) { + this.logger.info( + `Simple upload fallback succeeded for ${params.Key}`, + ); + } + } catch (error) { + this.logger.error( + `Upload failed for ${params.Key}: ${ + error instanceof Error ? error.message : String(error) + }`, + ); + throw error; + } }, absoluteFilesToUpload, { concurrencyLimit: 10 }, @@ -373,17 +559,21 @@ export class AwsS3Publish implements PublisherBase { await bulkStorageOperation( async relativeFilePath => { - return await this.storageClient.send( - new DeleteObjectCommand({ - Bucket: this.bucketName, - Key: relativeFilePath, - }), + return this.retryOperation( + async () => { + const deleteCommand = new DeleteObjectCommand({ + Bucket: this.bucketName, + Key: relativeFilePath, + }); + return this.storageClient.send(deleteCommand); + }, + 'DeleteObject', + this.maxAttempts, ); }, staleFiles, { concurrencyLimit: 10 }, ); - this.logger.info( `Successfully deleted stale files for Entity ${entity.metadata.name}. Total number of files: ${staleFiles.length}`, ); @@ -391,6 +581,13 @@ export class AwsS3Publish implements PublisherBase { const errorMessage = `Unable to delete file(s) from AWS S3. ${error}`; this.logger.error(errorMessage); } + const publishEndTime = Date.now(); + const publishDurationMs = publishEndTime - publishStartTime; + this.logger.info( + `Successfully published ${objects.length} files for ${ + entity.metadata.name + } in ${Math.round(publishDurationMs / 1000)}s`, + ); return { objects }; } @@ -413,11 +610,16 @@ export class AwsS3Publish implements PublisherBase { } try { - const resp = await this.storageClient.send( - new GetObjectCommand({ - Bucket: this.bucketName, - Key: `${entityRootDir}/techdocs_metadata.json`, - }), + const resp = await this.retryOperation( + async () => { + const getCommand = new GetObjectCommand({ + Bucket: this.bucketName, + Key: `${entityRootDir}/techdocs_metadata.json`, + }); + return this.storageClient.send(getCommand); + }, + 'GetTechDocsMetadata', + this.maxAttempts, ); const techdocsMetadataJson = await streamToBuffer( @@ -598,12 +800,18 @@ export class AwsS3Publish implements PublisherBase { let allObjects: ListObjectsV2CommandOutput; // Iterate through every file in the root of the publisher. do { - allObjects = await this.storageClient.send( - new ListObjectsV2Command({ - Bucket: this.bucketName, - ContinuationToken: nextContinuation, - ...(prefix ? { Prefix: prefix } : {}), - }), + const currentToken = nextContinuation; + allObjects = await this.retryOperation( + async () => { + const listCommand = new ListObjectsV2Command({ + Bucket: this.bucketName, + ContinuationToken: currentToken, + ...(prefix ? { Prefix: prefix } : {}), + }); + return this.storageClient.send(listCommand); + }, + 'GetAllObjects', + this.maxAttempts, ); objects.push( ...(allObjects.Contents || []).map(f => f.Key || '').filter(f => !!f), diff --git a/plugins/techdocs-react/CHANGELOG.md b/plugins/techdocs-react/CHANGELOG.md index c27a199b51..1763386235 100644 --- a/plugins/techdocs-react/CHANGELOG.md +++ b/plugins/techdocs-react/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-techdocs-react +## 1.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/core-plugin-api@1.12.1 + +## 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 + ## 1.3.5 ### Patch Changes diff --git a/plugins/techdocs-react/package.json b/plugins/techdocs-react/package.json index a5a461c66c..44bed4646f 100644 --- a/plugins/techdocs-react/package.json +++ b/plugins/techdocs-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-react", - "version": "1.3.5", + "version": "1.3.6", "description": "Shared frontend utilities for TechDocs and Addons", "backstage": { "role": "web-library", diff --git a/plugins/techdocs-react/report.api.md b/plugins/techdocs-react/report.api.md index 62ac6375fa..eb6d0dccb3 100644 --- a/plugins/techdocs-react/report.api.md +++ b/plugins/techdocs-react/report.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 { ApiRef } from '@backstage/core-plugin-api'; +import { ApiRef } from '@backstage/frontend-plugin-api'; import { AsyncState } from 'react-use/esm/useAsync'; import { ComponentType } from 'react'; import { CompoundEntityRef } from '@backstage/catalog-model'; diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index 244519721d..043bc49b6f 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,80 @@ # @backstage/plugin-techdocs +## 1.16.1 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- 5c33821: Fixed schema type for additionalAllowedURIProtocols +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- 592361e: The `techdocs` config is now marked as optional. +- Updated dependencies + - @backstage/integration@1.19.0 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/plugin-search-react@1.10.1 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/integration-react@1.2.13 + - @backstage/plugin-auth-react@0.1.22 + - @backstage/plugin-techdocs-react@1.3.6 + +## 1.16.1-next.2 + +### Patch Changes + +- f3f84f1: Minor extension type updates after frontend API bump +- Updated dependencies + - @backstage/frontend-plugin-api@0.13.2-next.1 + - @backstage/integration@1.18.3-next.1 + - @backstage/plugin-catalog-react@1.21.4-next.2 + - @backstage/core-components@0.18.4-next.2 + - @backstage/catalog-client@1.12.1 + - @backstage/catalog-model@1.7.6 + - @backstage/config@1.3.6 + - @backstage/core-plugin-api@1.12.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/integration-react@1.2.13-next.0 + - @backstage/theme@0.7.1-next.0 + - @backstage/plugin-auth-react@0.1.22-next.0 + - @backstage/plugin-search-common@1.2.21 + - @backstage/plugin-search-react@1.10.1-next.0 + - @backstage/plugin-techdocs-common@0.1.1 + - @backstage/plugin-techdocs-react@1.3.6-next.0 + +## 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 + +## 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 + ## 1.16.0 ### Minor Changes diff --git a/plugins/techdocs/config.d.ts b/plugins/techdocs/config.d.ts index 7294f52816..0e9b7abdda 100644 --- a/plugins/techdocs/config.d.ts +++ b/plugins/techdocs/config.d.ts @@ -19,7 +19,7 @@ export interface Config { * Configuration options for the techdocs plugin * @see http://backstage.io/docs/features/techdocs/configuration */ - techdocs: { + techdocs?: { /** * Documentation building process depends on the builder attr * @visibility frontend @@ -67,7 +67,7 @@ export interface Config { * @see: https://raw.githubusercontent.com/cure53/DOMPurify/master/src/regexp.ts * @visibility frontend */ - additionalAllowedURIProtocols?: string; + additionalAllowedURIProtocols?: string[]; }; }; } diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 18ac26eafe..2c052582d7 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs", - "version": "1.16.0", + "version": "1.16.1", "description": "The Backstage plugin that renders technical documentation for your components", "backstage": { "role": "frontend-plugin", @@ -62,7 +62,6 @@ "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", - "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", diff --git a/plugins/techdocs/report-alpha.api.md b/plugins/techdocs/report-alpha.api.md index 8a17625ac2..96c778b09b 100644 --- a/plugins/techdocs/report-alpha.api.md +++ b/plugins/techdocs/report-alpha.api.md @@ -13,12 +13,13 @@ import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; -import { IconComponent } from '@backstage/core-plugin-api'; +import { IconComponent } from '@backstage/frontend-plugin-api'; import { IconLinkVerticalProps } from '@backstage/core-components'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; -import { RouteRef } from '@backstage/frontend-plugin-api'; +import { RouteRef } from '@backstage/core-plugin-api'; +import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; import { SearchResultItemExtensionComponent } from '@backstage/plugin-search-react/alpha'; import { SearchResultItemExtensionPredicate } from '@backstage/plugin-search-react/alpha'; import { SearchResultListItemBlueprintParams } from '@backstage/plugin-search-react/alpha'; @@ -107,7 +108,7 @@ const _default: OverridableFrontendPlugin< | ExtensionDataRef | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; @@ -171,7 +172,7 @@ const _default: OverridableFrontendPlugin< defaultGroup?: [Error: `Use the 'group' param instead`]; group?: keyof defaultEntityContentGroups | (string & {}); loader: () => Promise; - routeRef?: RouteRef; + routeRef?: RouteRef_2; filter?: string | EntityPredicate | ((entity: Entity) => boolean); }; }>; @@ -223,7 +224,7 @@ const _default: OverridableFrontendPlugin< { title: string; icon: IconComponent; - routeRef: RouteRef; + routeRef: RouteRef_2; }, 'core.nav-item.target', {} @@ -232,7 +233,7 @@ const _default: OverridableFrontendPlugin< params: { title: string; icon: IconComponent; - routeRef: RouteRef; + routeRef: RouteRef_2; }; }>; 'page:techdocs': OverridableExtensionDefinition<{ @@ -248,7 +249,7 @@ const _default: OverridableFrontendPlugin< | ExtensionDataRef | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; @@ -259,7 +260,7 @@ const _default: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; loader: () => Promise; - routeRef?: RouteRef; + routeRef?: RouteRef_2; }; }>; 'page:techdocs/reader': OverridableExtensionDefinition<{ @@ -273,7 +274,7 @@ const _default: OverridableFrontendPlugin< | ExtensionDataRef | ExtensionDataRef | ExtensionDataRef< - RouteRef, + RouteRef_2, 'core.routing.ref', { optional: true; @@ -298,7 +299,7 @@ const _default: OverridableFrontendPlugin< defaultPath?: [Error: `Use the 'path' param instead`]; path: string; loader: () => Promise; - routeRef?: RouteRef; + routeRef?: RouteRef_2; }; }>; 'search-result-list-item:techdocs': OverridableExtensionDefinition<{ @@ -307,7 +308,6 @@ const _default: OverridableFrontendPlugin< lineClamp: number; asLink: boolean; asListItem: boolean; - } & { noTrack: boolean; }; configInput: { @@ -315,7 +315,6 @@ const _default: OverridableFrontendPlugin< lineClamp?: number | undefined; asListItem?: boolean | undefined; asLink?: boolean | undefined; - } & { noTrack?: boolean | undefined; }; output: ExtensionDataRef< @@ -327,15 +326,7 @@ const _default: OverridableFrontendPlugin< 'search.search-result-list-item.item', {} >; - inputs: { - [x: string]: ExtensionInput< - ExtensionDataRef, - { - singleton: boolean; - optional: boolean; - } - >; - }; + inputs: {}; kind: 'search-result-list-item'; name: undefined; params: SearchResultListItemBlueprintParams; @@ -351,7 +342,6 @@ export const techDocsSearchResultListItemExtension: OverridableExtensionDefiniti lineClamp: number; asLink: boolean; asListItem: boolean; - } & { noTrack: boolean; }; configInput: { @@ -359,7 +349,6 @@ export const techDocsSearchResultListItemExtension: OverridableExtensionDefiniti lineClamp?: number | undefined; asListItem?: boolean | undefined; asLink?: boolean | undefined; - } & { noTrack?: boolean | undefined; }; output: ExtensionDataRef< @@ -371,15 +360,7 @@ export const techDocsSearchResultListItemExtension: OverridableExtensionDefiniti 'search.search-result-list-item.item', {} >; - inputs: { - [x: string]: ExtensionInput< - ExtensionDataRef, - { - singleton: boolean; - optional: boolean; - } - >; - }; + inputs: {}; kind: 'search-result-list-item'; name: undefined; params: SearchResultListItemBlueprintParams; diff --git a/plugins/techdocs/report.api.md b/plugins/techdocs/report.api.md index 2369466674..e7e9e4b916 100644 --- a/plugins/techdocs/report.api.md +++ b/plugins/techdocs/report.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 { ApiRef } from '@backstage/core-plugin-api'; +import { ApiRef } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { CompoundEntityRef } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; diff --git a/plugins/techdocs/src/alpha/index.tsx b/plugins/techdocs/src/alpha/index.tsx index 96454600d9..d3fc94c7a8 100644 --- a/plugins/techdocs/src/alpha/index.tsx +++ b/plugins/techdocs/src/alpha/index.tsx @@ -29,11 +29,6 @@ import { discoveryApiRef, fetchApiRef, } from '@backstage/core-plugin-api'; -import { - compatWrapper, - convertLegacyRouteRef, - convertLegacyRouteRefs, -} from '@backstage/core-compat-api'; import { EntityContentBlueprint, EntityIconLinkBlueprint, @@ -123,10 +118,9 @@ export const techDocsSearchResultListItemExtension = const { TechDocsSearchResultListItem } = await import( '../search/components/TechDocsSearchResultListItem' ); - return props => - compatWrapper( - , - ); + return props => ( + + ); }, }); }, @@ -140,11 +134,11 @@ export const techDocsSearchResultListItemExtension = const techDocsPage = PageBlueprint.make({ params: { path: '/docs', - routeRef: convertLegacyRouteRef(rootRouteRef), + routeRef: rootRouteRef, loader: () => - import('../home/components/TechDocsIndexPage').then(m => - compatWrapper(), - ), + import('../home/components/TechDocsIndexPage').then(m => ( + + )), }, }); @@ -168,16 +162,14 @@ const techDocsReaderPage = PageBlueprint.makeWithOverrides({ return originalFactory({ path: '/docs/:namespace/:kind/:name', - routeRef: convertLegacyRouteRef(rootDocsRouteRef), + routeRef: rootDocsRouteRef, loader: async () => - await import('../Router').then(({ TechDocsReaderRouter }) => { - return compatWrapper( - - - {addons} - , - ); - }), + await import('../Router').then(({ TechDocsReaderRouter }) => ( + + + {addons} + + )), }); }, }); @@ -203,7 +195,7 @@ const techDocsEntityContent = EntityContentBlueprint.makeWithOverrides({ { path: 'docs', title: 'TechDocs', - routeRef: convertLegacyRouteRef(rootCatalogDocsRouteRef), + routeRef: rootCatalogDocsRouteRef, loader: () => import('../Router').then(({ EmbeddedDocsRouter }) => { const addons = context.inputs.addons.map(output => { @@ -212,14 +204,14 @@ const techDocsEntityContent = EntityContentBlueprint.makeWithOverrides({ attachTechDocsAddonComponentData(Addon, options); return ; }); - return compatWrapper( + return ( {addons} - , + ); }), }, @@ -241,7 +233,7 @@ const techDocsNavItem = NavItemBlueprint.make({ params: { icon: LibraryBooks, title: 'Docs', - routeRef: convertLegacyRouteRef(rootRouteRef), + routeRef: rootRouteRef, }, }); @@ -260,9 +252,9 @@ export default createFrontendPlugin({ techDocsEntityContentEmptyState, techDocsSearchResultListItemExtension, ], - routes: convertLegacyRouteRefs({ + routes: { root: rootRouteRef, docRoot: rootDocsRouteRef, entityContent: rootCatalogDocsRouteRef, - }), + }, }); diff --git a/plugins/techdocs/src/reader/transformers/handleMetaRedirects.test.ts b/plugins/techdocs/src/reader/transformers/handleMetaRedirects.test.ts index 0be1773846..00180464cf 100644 --- a/plugins/techdocs/src/reader/transformers/handleMetaRedirects.test.ts +++ b/plugins/techdocs/src/reader/transformers/handleMetaRedirects.test.ts @@ -21,21 +21,13 @@ import { screen } from '@testing-library/react'; describe('handleMetaRedirects', () => { const navigate = jest.fn(); - const setUpNewTestShadowDom = async ( - html: string, - rootHref: string, - rootPath: string, - ) => { + const setUpNewTestShadowDom = async (html: string, rootHref: string) => { const entityName = 'testEntity'; - // Mock window.location.href for each test - Object.defineProperty(window, 'location', { - value: { - href: rootHref, - pathname: rootPath, - hostname: 'localhost', - }, - writable: true, - }); + // Use history.replaceState to change location (jsdom 27+ doesn't allow redefining location) + // Jest's jsdom starts at http://localhost/, so replaceState updates the pathname while + // keeping hostname and origin as 'localhost'. + const url = new URL(rootHref); + history.replaceState(null, '', `${url.pathname}${url.search}${url.hash}`); return await createTestShadowDom(html, { preTransformers: [], postTransformers: [handleMetaRedirects(navigate, entityName)], @@ -55,7 +47,6 @@ describe('handleMetaRedirects', () => { await setUpNewTestShadowDom( ``, 'http://localhost/docs/default/component/testEntity/subpath', - '/docs/default/component/testEntity/subpath', ); expect( @@ -73,7 +64,6 @@ describe('handleMetaRedirects', () => { await setUpNewTestShadowDom( ``, 'http://localhost/docs/default/component/testEntity/subpath', - '/docs/default/component/testEntity/subpath', ); expect( @@ -91,7 +81,6 @@ describe('handleMetaRedirects', () => { await setUpNewTestShadowDom( ``, 'http://localhost/docs/default/component/testEntity/subpath', - '/docs/default/component/testEntity/subpath', ); expect( @@ -107,7 +96,6 @@ describe('handleMetaRedirects', () => { await setUpNewTestShadowDom( ``, 'http://localhost/docs/default/component/testEntity/subpath', - '/docs/default/component/testEntity/subpath', ); jest.runAllTimers(); diff --git a/plugins/user-settings-backend/CHANGELOG.md b/plugins/user-settings-backend/CHANGELOG.md index 116fa51208..a34907c701 100644 --- a/plugins/user-settings-backend/CHANGELOG.md +++ b/plugins/user-settings-backend/CHANGELOG.md @@ -1,5 +1,43 @@ # @backstage/plugin-user-settings-backend +## 0.3.9 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + - @backstage/backend-defaults@0.14.0 + - @backstage/plugin-signals-node@0.1.27 + - @backstage/backend-plugin-api@1.6.0 + +## 0.3.9-next.1 + +### Patch Changes + +- de96a60: chore(deps): bump `express` from 4.21.2 to 4.22.0 +- Updated dependencies + - @backstage/backend-defaults@0.14.0-next.1 + - @backstage/plugin-auth-node@0.6.10-next.1 + - @backstage/plugin-signals-node@0.1.27-next.1 + - @backstage/backend-plugin-api@1.6.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-user-settings-common@0.0.1 + +## 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 + ## 0.3.8 ### Patch Changes diff --git a/plugins/user-settings-backend/package.json b/plugins/user-settings-backend/package.json index bae5ca8066..689fbd8926 100644 --- a/plugins/user-settings-backend/package.json +++ b/plugins/user-settings-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings-backend", - "version": "0.3.8", + "version": "0.3.9", "description": "The Backstage backend plugin to manage user settings", "backstage": { "role": "backend-plugin", @@ -55,7 +55,7 @@ "@backstage/plugin-signals-node": "workspace:^", "@backstage/plugin-user-settings-common": "workspace:^", "@backstage/types": "workspace:^", - "express": "^4.17.1", + "express": "^4.22.0", "express-promise-router": "^4.1.0", "knex": "^3.0.0" }, diff --git a/plugins/user-settings/CHANGELOG.md b/plugins/user-settings/CHANGELOG.md index 672bc97697..08368f5a3c 100644 --- a/plugins/user-settings/CHANGELOG.md +++ b/plugins/user-settings/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/plugin-user-settings +## 0.8.30 + +### Patch Changes + +- d02db50: Remove unnecessary use of `compatWrapper` and `convertLegacyRouteRef`(s) for the new frontend system. +- Updated dependencies + - @backstage/core-app-api@1.19.3 + - @backstage/frontend-plugin-api@0.13.2 + - @backstage/core-components@0.18.4 + - @backstage/plugin-catalog-react@1.21.4 + - @backstage/core-plugin-api@1.12.1 + - @backstage/theme@0.7.1 + - @backstage/plugin-signals-react@0.0.18 + +## 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 + ## 0.8.29 ### Patch Changes diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 508ca6000d..a920c248e7 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings", - "version": "0.8.29", + "version": "0.8.30", "description": "A Backstage plugin that provides a settings page", "backstage": { "role": "frontend-plugin", @@ -57,7 +57,6 @@ "dependencies": { "@backstage/catalog-model": "workspace:^", "@backstage/core-app-api": "workspace:^", - "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", diff --git a/plugins/user-settings/report-alpha.api.md b/plugins/user-settings/report-alpha.api.md index a900e5600c..73ef5cfa3d 100644 --- a/plugins/user-settings/report-alpha.api.md +++ b/plugins/user-settings/report-alpha.api.md @@ -7,17 +7,18 @@ import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; -import { IconComponent } from '@backstage/core-plugin-api'; +import { IconComponent } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; import { RouteRef } from '@backstage/frontend-plugin-api'; -import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +import { RouteRef as RouteRef_2 } from '@backstage/core-plugin-api'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) const _default: OverridableFrontendPlugin< { - root: RouteRef; + root: RouteRef_2; }, {}, { diff --git a/plugins/user-settings/report.api.md b/plugins/user-settings/report.api.md index c99765bc67..77670bb357 100644 --- a/plugins/user-settings/report.api.md +++ b/plugins/user-settings/report.api.md @@ -5,7 +5,7 @@ ```ts import { ApiRef } from '@backstage/core-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; -import { BackstageUserIdentity } from '@backstage/core-plugin-api'; +import { BackstageUserIdentity } from '@backstage/frontend-plugin-api'; import { DiscoveryApi } from '@backstage/core-plugin-api'; import { ElementType } from 'react'; import { ErrorApi } from '@backstage/core-plugin-api'; diff --git a/plugins/user-settings/src/alpha.tsx b/plugins/user-settings/src/alpha.tsx index e71c61febc..bea75bbdd8 100644 --- a/plugins/user-settings/src/alpha.tsx +++ b/plugins/user-settings/src/alpha.tsx @@ -20,11 +20,6 @@ import { PageBlueprint, NavItemBlueprint, } from '@backstage/frontend-plugin-api'; -import { - convertLegacyRouteRef, - convertLegacyRouteRefs, - compatWrapper, -} from '@backstage/core-compat-api'; import SettingsIcon from '@material-ui/icons/Settings'; import { settingsRouteRef } from './plugin'; @@ -40,17 +35,15 @@ const userSettingsPage = PageBlueprint.makeWithOverrides({ factory(originalFactory, { inputs }) { return originalFactory({ path: '/settings', - routeRef: convertLegacyRouteRef(settingsRouteRef), + routeRef: settingsRouteRef, loader: () => - import('./components/SettingsPage').then(m => - compatWrapper( - , - ), - ), + import('./components/SettingsPage').then(m => ( + + )), }); }, }); @@ -58,7 +51,7 @@ const userSettingsPage = PageBlueprint.makeWithOverrides({ /** @alpha */ export const settingsNavItem = NavItemBlueprint.make({ params: { - routeRef: convertLegacyRouteRef(settingsRouteRef), + routeRef: settingsRouteRef, title: 'Settings', icon: SettingsIcon, }, @@ -71,7 +64,7 @@ export default createFrontendPlugin({ pluginId: 'user-settings', info: { packageJson: () => import('../package.json') }, extensions: [userSettingsPage, settingsNavItem], - routes: convertLegacyRouteRefs({ + routes: { root: settingsRouteRef, - }), + }, }); diff --git a/scripts/patch-release-for-pr.js b/scripts/patch-release-for-pr.js index d70eaa8ac5..c0caa8ee75 100755 --- a/scripts/patch-release-for-pr.js +++ b/scripts/patch-release-for-pr.js @@ -20,13 +20,14 @@ const path = require('path'); const semver = require('semver'); const { Octokit } = require('@octokit/rest'); const { execFile: execFileCb } = require('child_process'); -const { promisify } = require('util'); +const { promisify, parseArgs } = require('util'); const execFile = promisify(execFileCb); const owner = 'backstage'; const repo = 'backstage'; const rootDir = path.resolve(__dirname, '..'); +const PATCH_FILE_PATTERN = /^pr-(\d+)\.txt$/; const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN, @@ -81,15 +82,120 @@ async function findCurrentReleaseVersion() { throw new Error('No stable release found'); } -async function main(args) { - const prNumbers = args.map(s => { - const num = parseInt(s, 10); - if (!Number.isInteger(num)) { - throw new Error(`Must provide valid PR number arguments, got ${s}`); - } - return num; +/** + * Parses command-line arguments and determines which patches to use. + * Returns an object with prNumbers and descriptions, or null if should exit early. + */ +async function parsePatchArguments(args) { + const { values, positionals } = parseArgs({ + args, + options: { + 'from-dir': { + type: 'boolean', + short: 'd', + }, + }, + allowPositionals: true, }); - console.log(`PR number(s): ${prNumbers.join(', ')}`); + + const useDirectory = values['from-dir'] === true; + + let prNumbers; + let descriptions; + + if (useDirectory) { + // Read patches from directory + const patchesFromDir = await readPatchesFromDirectory(); + + if (patchesFromDir) { + // Use patches from directory + prNumbers = patchesFromDir.map(p => p.prNumber); + descriptions = patchesFromDir.map(p => p.description); + console.log(`Reading patches from .patches/ directory`); + console.log(`PR number(s): ${prNumbers.join(', ')}`); + return { prNumbers, descriptions }; + } + // No patches found in directory - exit early + return null; + } else if (positionals.length > 0) { + // Use command-line arguments + prNumbers = positionals.map(s => { + const num = parseInt(s, 10); + if (!Number.isInteger(num)) { + throw new Error(`Must provide valid PR number arguments, got ${s}`); + } + return num; + }); + console.log(`Using command-line arguments`); + console.log(`PR number(s): ${prNumbers.join(', ')}`); + return { prNumbers, descriptions: undefined }; + } + // No patches found and no command-line args - exit early + return null; +} + +/** + * Reads patch files from .patches/ directory and returns PR numbers with descriptions. + * Returns null if directory doesn't exist or contains no matching files. + */ +async function readPatchesFromDirectory() { + const patchesDir = path.resolve(rootDir, '.patches'); + + try { + const exists = await fs.pathExists(patchesDir); + if (!exists) { + return null; + } + + const files = await fs.readdir(patchesDir); + const patchFiles = files.filter(file => PATCH_FILE_PATTERN.test(file)); + + if (patchFiles.length === 0) { + return null; + } + + const patches = []; + for (const file of patchFiles) { + const match = file.match(PATCH_FILE_PATTERN); + const prNumber = parseInt(match[1], 10); + const filePath = path.resolve(patchesDir, file); + const content = await fs.readFile(filePath, 'utf8'); + const description = content.trim() || ''; + patches.push({ prNumber, description }); + } + + // Sort by PR number for consistent ordering + patches.sort((a, b) => a.prNumber - b.prNumber); + + return patches; + } catch { + // If there's an error reading the directory, return null to fall back to CLI args + return null; + } +} + +async function ensureCommitExists(sha) { + try { + await run('git', 'cat-file', '-e', sha); + } catch { + console.log(`Commit ${sha} not found locally, fetching...`); + + try { + await run('git', 'fetch', 'origin', sha); + } catch (error) { + throw new Error(`Failed to fetch commit ${sha}: ${error}`); + } + } +} + +async function main(args) { + const patchInfo = await parsePatchArguments(args); + if (!patchInfo) { + // No patches found and no command-line args - exit early + return; + } + + const { prNumbers, descriptions } = patchInfo; if (await run('git', 'status', '--porcelain')) { throw new Error('Cannot run with a dirty working tree'); @@ -101,6 +207,16 @@ async function main(args) { await run('git', 'fetch'); const patchBranch = `patch/v${release}`; + + // Output patch branch for CI workflows to capture + if (process.env.PATCH_RELEASE_BRANCH && process.env.GITHUB_OUTPUT) { + // Use native fs for appendFileSync (fs-extra doesn't have sync version) + const nativeFs = require('fs'); + nativeFs.appendFileSync( + process.env.GITHUB_OUTPUT, + `patch_branch=${patchBranch}\n`, + ); + } try { await run('git', 'checkout', `origin/${patchBranch}`); } catch { @@ -109,8 +225,24 @@ async function main(args) { } // Create new branch, apply changes from all commits on PR branch, commit, push - const branchName = `patch-release-pr-${prNumbers.join('-')}`; - await run('git', 'checkout', '-b', branchName); + // Allow fixed branch name via environment variable for CI workflows + const branchName = + process.env.PATCH_RELEASE_BRANCH || + `patch-release-pr-${prNumbers.join('-')}`; + + // Check if branch already exists (for CI workflows using fixed branch name) + try { + await run( + 'git', + 'show-ref', + '--verify', + '--quiet', + `refs/heads/${branchName}`, + ); + await run('git', 'checkout', branchName); + } catch { + await run('git', 'checkout', '-b', branchName); + } for (const prNumber of prNumbers) { const { data } = await octokit.pulls.get({ @@ -127,6 +259,9 @@ async function main(args) { if (!baseSha) { throw new Error('base sha not available'); } + + await ensureCommitExists(headSha); + const mergeBaseSha = await run('git', 'merge-base', headSha, baseSha); const logLines = await run( @@ -155,6 +290,10 @@ async function main(args) { console.log('Running "yarn release" ...'); await run('yarn', 'release'); + // Note: Patch files are not deleted here because this script runs in the patch + // release branch, not master. The cleanup_patch-files.yml workflow handles + // deletion from master after the patch release PR is merged. + await run('git', 'add', '.'); await run( 'git', @@ -165,18 +304,41 @@ async function main(args) { 'Generate Release', ); - await run('git', 'push', 'origin', '-u', branchName); + // Use force push if using a specific branch name (for CI workflows) + if (process.env.PATCH_RELEASE_BRANCH) { + await run('git', 'push', 'origin', '-u', '--force-with-lease', branchName); + } else { + await run('git', 'push', 'origin', '-u', branchName); + } + + // Generate PR body + let body; + if (descriptions) { + const descriptionList = descriptions + .map((desc, index) => { + const prNumber = prNumbers[index]; + const prLink = `https://github.com/${owner}/${repo}/pull/${prNumber}`; + return `- ${desc} ([#${prNumber}](${prLink}))`; + }) + .join('\n'); + body = `This patch release includes the following fixes:\n\n${descriptionList}`; + } else { + body = 'This release fixes an issue where'; + } const params = new URLSearchParams({ expand: 1, - body: 'This release fixes an issue where', + body: body, title: `Patch release of ${prNumbers.map(nr => `#${nr}`).join(', ')}`, }); const url = `https://github.com/backstage/backstage/compare/${patchBranch}...${branchName}?${params}`; - console.log(`Opening ${url} ...`); + console.log(`PR URL: ${url}`); - await run('open', url); + // Only open URL if not in CI (no PATCH_RELEASE_BRANCH env var means manual run) + if (!process.env.PATCH_RELEASE_BRANCH) { + await run('open', url); + } } main(process.argv.slice(2)).catch(error => { diff --git a/scripts/plugin-directory-audit.js b/scripts/plugin-directory-audit.js new file mode 100644 index 0000000000..7e28ae2b57 --- /dev/null +++ b/scripts/plugin-directory-audit.js @@ -0,0 +1,74 @@ +#!/usr/bin/env node +/* + * 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. + */ + +/* eslint-disable @backstage/no-undeclared-imports */ + +// This script is used to audit the list of plugins in the Plugin Directory: https://backstage.io/plugins + +const { resolve } = require('path'); +const fs = require('fs-extra'); +const yaml = require('js-yaml'); + +async function getNpmPackage(npmPackageName) { + const response = await fetch(`https://registry.npmjs.com/${npmPackageName}`); + const json = await response.json(); + return json; +} + +function getAge(npmModified) { + const ageDif = Date.now() - new Date(npmModified).getTime(); + return Math.round(ageDif / (1000 * 60 * 60 * 24)); +} + +async function main() { + const rootPath = resolve(__dirname, '..'); + const pluginDataPath = resolve(rootPath, 'microsite/data/plugins'); + + console.log(__dirname, rootPath, pluginDataPath); + + const pluginDataFiles = fs.readdirSync(pluginDataPath); + + const pluginsData = []; + for (const pluginDataFile of pluginDataFiles) { + const pluginDataFilePath = resolve(pluginDataPath, pluginDataFile); + const pluginDataYaml = yaml.load( + fs.readFileSync(pluginDataFilePath, { encoding: 'utf-8' }), + ); + + console.log( + `Auditing - ${pluginDataYaml.title} by ${pluginDataYaml.author} - ${pluginDataYaml.npmPackageName}`, + ); + + const npmPackage = await getNpmPackage(pluginDataYaml.npmPackageName); + + const pluginData = { + npmPackageName: pluginDataYaml.npmPackageName, + npmCreated: npmPackage.time?.created, + npmModified: npmPackage.time?.modified, + age: getAge(npmPackage.time?.modified), + }; + + pluginsData.push(pluginData); + } + + console.table(pluginsData); +} + +main(process.argv.slice(2)).catch(error => { + console.error(error.stack || error); + process.exit(1); +}); diff --git a/yarn.lock b/yarn.lock index b1c15c04e8..393cd37bd7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,6 +12,13 @@ __metadata: languageName: node linkType: hard +"@acemir/cssom@npm:^0.9.28": + version: 0.9.28 + resolution: "@acemir/cssom@npm:0.9.28" + checksum: 10/429faa2af7bffdcf3311e984f6b8ced77bf58d936e0e9a22c2fc892a84e54d2c8cbd71ae3a979ef54241509ac7c866cb880d919e3e713e64c865a1cd4ede3a1f + languageName: node + linkType: hard + "@adobe/css-tools@npm:^4.4.0": version: 4.4.0 resolution: "@adobe/css-tools@npm:4.4.0" @@ -263,6 +270,52 @@ __metadata: languageName: node linkType: hard +"@asamuzakjp/css-color@npm:^3.2.0": + version: 3.2.0 + resolution: "@asamuzakjp/css-color@npm:3.2.0" + dependencies: + "@csstools/css-calc": "npm:^2.1.3" + "@csstools/css-color-parser": "npm:^3.0.9" + "@csstools/css-parser-algorithms": "npm:^3.0.4" + "@csstools/css-tokenizer": "npm:^3.0.3" + lru-cache: "npm:^10.4.3" + checksum: 10/870f661460173174fef8bfebea0799ba26566f3aa7b307e5adabb7aae84fed2da68e40080104ed0c83b43c5be632ee409e65396af13bfe948a3ef4c2c729ecd9 + languageName: node + linkType: hard + +"@asamuzakjp/css-color@npm:^4.1.0": + version: 4.1.0 + resolution: "@asamuzakjp/css-color@npm:4.1.0" + dependencies: + "@csstools/css-calc": "npm:^2.1.4" + "@csstools/css-color-parser": "npm:^3.1.0" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + lru-cache: "npm:^11.2.2" + checksum: 10/e76b57fff34b958e224662d2afb19d23bc6c95d6ea40055e23a8f7417ae79904b7741655bbe6a883c09d5ee68ebb20e9313036aed3680fe85e383e0527397566 + languageName: node + linkType: hard + +"@asamuzakjp/dom-selector@npm:^6.7.6": + version: 6.7.6 + resolution: "@asamuzakjp/dom-selector@npm:6.7.6" + dependencies: + "@asamuzakjp/nwsapi": "npm:^2.3.9" + bidi-js: "npm:^1.0.3" + css-tree: "npm:^3.1.0" + is-potential-custom-element-name: "npm:^1.0.1" + lru-cache: "npm:^11.2.4" + checksum: 10/91a479f5f59a3b1b23f46407d874882c05b50e72316e57af105c88603fb1008120c46c7fe7504c5be556ea4c9a68ee10aee43433bd8d34f000b9aaf6dcae4d5c + languageName: node + linkType: hard + +"@asamuzakjp/nwsapi@npm:^2.3.9": + version: 2.3.9 + resolution: "@asamuzakjp/nwsapi@npm:2.3.9" + checksum: 10/95a6d1c102e1117fe818da087fcc5b914d23e0699855991bae50b891435dd1945ad7d384198f8bcf616207fd85b7ec32e3db6b96e9309d84c6903b8dc4151e34 + languageName: node + linkType: hard + "@asyncapi/avro-schema-parser@npm:^3.0.24": version: 3.0.24 resolution: "@asyncapi/avro-schema-parser@npm:3.0.24" @@ -712,51 +765,51 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/client-sesv2@npm:^3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/client-sesv2@npm:3.911.0" +"@aws-sdk/client-sesv2@npm:^3.839.0, @aws-sdk/client-sesv2@npm:^3.911.0": + version: 3.946.0 + resolution: "@aws-sdk/client-sesv2@npm:3.946.0" dependencies: "@aws-crypto/sha256-browser": "npm:5.2.0" "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/core": "npm:3.911.0" - "@aws-sdk/credential-provider-node": "npm:3.911.0" - "@aws-sdk/middleware-host-header": "npm:3.910.0" - "@aws-sdk/middleware-logger": "npm:3.910.0" - "@aws-sdk/middleware-recursion-detection": "npm:3.910.0" - "@aws-sdk/middleware-user-agent": "npm:3.911.0" - "@aws-sdk/region-config-resolver": "npm:3.910.0" - "@aws-sdk/signature-v4-multi-region": "npm:3.911.0" - "@aws-sdk/types": "npm:3.910.0" - "@aws-sdk/util-endpoints": "npm:3.910.0" - "@aws-sdk/util-user-agent-browser": "npm:3.910.0" - "@aws-sdk/util-user-agent-node": "npm:3.911.0" - "@smithy/config-resolver": "npm:^4.3.2" - "@smithy/core": "npm:^3.16.1" - "@smithy/fetch-http-handler": "npm:^5.3.3" - "@smithy/hash-node": "npm:^4.2.2" - "@smithy/invalid-dependency": "npm:^4.2.2" - "@smithy/middleware-content-length": "npm:^4.2.2" - "@smithy/middleware-endpoint": "npm:^4.3.3" - "@smithy/middleware-retry": "npm:^4.4.3" - "@smithy/middleware-serde": "npm:^4.2.2" - "@smithy/middleware-stack": "npm:^4.2.2" - "@smithy/node-config-provider": "npm:^4.3.2" - "@smithy/node-http-handler": "npm:^4.4.1" - "@smithy/protocol-http": "npm:^5.3.2" - "@smithy/smithy-client": "npm:^4.8.1" - "@smithy/types": "npm:^4.7.1" - "@smithy/url-parser": "npm:^4.2.2" + "@aws-sdk/core": "npm:3.946.0" + "@aws-sdk/credential-provider-node": "npm:3.946.0" + "@aws-sdk/middleware-host-header": "npm:3.936.0" + "@aws-sdk/middleware-logger": "npm:3.936.0" + "@aws-sdk/middleware-recursion-detection": "npm:3.936.0" + "@aws-sdk/middleware-user-agent": "npm:3.946.0" + "@aws-sdk/region-config-resolver": "npm:3.936.0" + "@aws-sdk/signature-v4-multi-region": "npm:3.946.0" + "@aws-sdk/types": "npm:3.936.0" + "@aws-sdk/util-endpoints": "npm:3.936.0" + "@aws-sdk/util-user-agent-browser": "npm:3.936.0" + "@aws-sdk/util-user-agent-node": "npm:3.946.0" + "@smithy/config-resolver": "npm:^4.4.3" + "@smithy/core": "npm:^3.18.7" + "@smithy/fetch-http-handler": "npm:^5.3.6" + "@smithy/hash-node": "npm:^4.2.5" + "@smithy/invalid-dependency": "npm:^4.2.5" + "@smithy/middleware-content-length": "npm:^4.2.5" + "@smithy/middleware-endpoint": "npm:^4.3.14" + "@smithy/middleware-retry": "npm:^4.4.14" + "@smithy/middleware-serde": "npm:^4.2.6" + "@smithy/middleware-stack": "npm:^4.2.5" + "@smithy/node-config-provider": "npm:^4.3.5" + "@smithy/node-http-handler": "npm:^4.4.5" + "@smithy/protocol-http": "npm:^5.3.5" + "@smithy/smithy-client": "npm:^4.9.10" + "@smithy/types": "npm:^4.9.0" + "@smithy/url-parser": "npm:^4.2.5" "@smithy/util-base64": "npm:^4.3.0" "@smithy/util-body-length-browser": "npm:^4.2.0" "@smithy/util-body-length-node": "npm:^4.2.1" - "@smithy/util-defaults-mode-browser": "npm:^4.3.2" - "@smithy/util-defaults-mode-node": "npm:^4.2.3" - "@smithy/util-endpoints": "npm:^3.2.2" - "@smithy/util-middleware": "npm:^4.2.2" - "@smithy/util-retry": "npm:^4.2.2" + "@smithy/util-defaults-mode-browser": "npm:^4.3.13" + "@smithy/util-defaults-mode-node": "npm:^4.2.16" + "@smithy/util-endpoints": "npm:^3.2.5" + "@smithy/util-middleware": "npm:^4.2.5" + "@smithy/util-retry": "npm:^4.2.5" "@smithy/util-utf8": "npm:^4.2.0" tslib: "npm:^2.6.2" - checksum: 10/27831e2fe821120a1a252f9bbc00bc8b9669d38cb112e369a1690c2f7aba1bb3e7b8743acd235db1b7cbf62e4d27abced22fa068b3962e1f5313665b33ed0084 + checksum: 10/da96e92e19e3e0d1815d311820dc9ad6f35b04f19247395d82b188bafc86d53ac7dc051f84eff5cf4370f1d402d537748c262af93f45009a6fb78eb690f576ae languageName: node linkType: hard @@ -906,49 +959,49 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/client-sso@npm:3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/client-sso@npm:3.911.0" +"@aws-sdk/client-sso@npm:3.946.0": + version: 3.946.0 + resolution: "@aws-sdk/client-sso@npm:3.946.0" dependencies: "@aws-crypto/sha256-browser": "npm:5.2.0" "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/core": "npm:3.911.0" - "@aws-sdk/middleware-host-header": "npm:3.910.0" - "@aws-sdk/middleware-logger": "npm:3.910.0" - "@aws-sdk/middleware-recursion-detection": "npm:3.910.0" - "@aws-sdk/middleware-user-agent": "npm:3.911.0" - "@aws-sdk/region-config-resolver": "npm:3.910.0" - "@aws-sdk/types": "npm:3.910.0" - "@aws-sdk/util-endpoints": "npm:3.910.0" - "@aws-sdk/util-user-agent-browser": "npm:3.910.0" - "@aws-sdk/util-user-agent-node": "npm:3.911.0" - "@smithy/config-resolver": "npm:^4.3.2" - "@smithy/core": "npm:^3.16.1" - "@smithy/fetch-http-handler": "npm:^5.3.3" - "@smithy/hash-node": "npm:^4.2.2" - "@smithy/invalid-dependency": "npm:^4.2.2" - "@smithy/middleware-content-length": "npm:^4.2.2" - "@smithy/middleware-endpoint": "npm:^4.3.3" - "@smithy/middleware-retry": "npm:^4.4.3" - "@smithy/middleware-serde": "npm:^4.2.2" - "@smithy/middleware-stack": "npm:^4.2.2" - "@smithy/node-config-provider": "npm:^4.3.2" - "@smithy/node-http-handler": "npm:^4.4.1" - "@smithy/protocol-http": "npm:^5.3.2" - "@smithy/smithy-client": "npm:^4.8.1" - "@smithy/types": "npm:^4.7.1" - "@smithy/url-parser": "npm:^4.2.2" + "@aws-sdk/core": "npm:3.946.0" + "@aws-sdk/middleware-host-header": "npm:3.936.0" + "@aws-sdk/middleware-logger": "npm:3.936.0" + "@aws-sdk/middleware-recursion-detection": "npm:3.936.0" + "@aws-sdk/middleware-user-agent": "npm:3.946.0" + "@aws-sdk/region-config-resolver": "npm:3.936.0" + "@aws-sdk/types": "npm:3.936.0" + "@aws-sdk/util-endpoints": "npm:3.936.0" + "@aws-sdk/util-user-agent-browser": "npm:3.936.0" + "@aws-sdk/util-user-agent-node": "npm:3.946.0" + "@smithy/config-resolver": "npm:^4.4.3" + "@smithy/core": "npm:^3.18.7" + "@smithy/fetch-http-handler": "npm:^5.3.6" + "@smithy/hash-node": "npm:^4.2.5" + "@smithy/invalid-dependency": "npm:^4.2.5" + "@smithy/middleware-content-length": "npm:^4.2.5" + "@smithy/middleware-endpoint": "npm:^4.3.14" + "@smithy/middleware-retry": "npm:^4.4.14" + "@smithy/middleware-serde": "npm:^4.2.6" + "@smithy/middleware-stack": "npm:^4.2.5" + "@smithy/node-config-provider": "npm:^4.3.5" + "@smithy/node-http-handler": "npm:^4.4.5" + "@smithy/protocol-http": "npm:^5.3.5" + "@smithy/smithy-client": "npm:^4.9.10" + "@smithy/types": "npm:^4.9.0" + "@smithy/url-parser": "npm:^4.2.5" "@smithy/util-base64": "npm:^4.3.0" "@smithy/util-body-length-browser": "npm:^4.2.0" "@smithy/util-body-length-node": "npm:^4.2.1" - "@smithy/util-defaults-mode-browser": "npm:^4.3.2" - "@smithy/util-defaults-mode-node": "npm:^4.2.3" - "@smithy/util-endpoints": "npm:^3.2.2" - "@smithy/util-middleware": "npm:^4.2.2" - "@smithy/util-retry": "npm:^4.2.2" + "@smithy/util-defaults-mode-browser": "npm:^4.3.13" + "@smithy/util-defaults-mode-node": "npm:^4.2.16" + "@smithy/util-endpoints": "npm:^3.2.5" + "@smithy/util-middleware": "npm:^4.2.5" + "@smithy/util-retry": "npm:^4.2.5" "@smithy/util-utf8": "npm:^4.2.0" tslib: "npm:^2.6.2" - checksum: 10/63c3f054251f9b2d823fa65c3c82887230a62bdf364af8f8b3ea5ad22995ea97058bb8ffbfacf2724c058acea0c437fc8b2bc5f21a098a03a3550df53a02e258 + checksum: 10/02f43ce6418d22ad1d4347d62eef1dc21df6945b2e2cc52f6b266488fa9d0ac51262392a5a1cfa8e8d600ffb160d3a2e1c02406a72bda3dc3dc86af4886dcbf8 languageName: node linkType: hard @@ -1018,24 +1071,24 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/core@npm:3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/core@npm:3.911.0" +"@aws-sdk/core@npm:3.946.0": + version: 3.946.0 + resolution: "@aws-sdk/core@npm:3.946.0" dependencies: - "@aws-sdk/types": "npm:3.910.0" - "@aws-sdk/xml-builder": "npm:3.911.0" - "@smithy/core": "npm:^3.16.1" - "@smithy/node-config-provider": "npm:^4.3.2" - "@smithy/property-provider": "npm:^4.2.2" - "@smithy/protocol-http": "npm:^5.3.2" - "@smithy/signature-v4": "npm:^5.3.2" - "@smithy/smithy-client": "npm:^4.8.1" - "@smithy/types": "npm:^4.7.1" + "@aws-sdk/types": "npm:3.936.0" + "@aws-sdk/xml-builder": "npm:3.930.0" + "@smithy/core": "npm:^3.18.7" + "@smithy/node-config-provider": "npm:^4.3.5" + "@smithy/property-provider": "npm:^4.2.5" + "@smithy/protocol-http": "npm:^5.3.5" + "@smithy/signature-v4": "npm:^5.3.5" + "@smithy/smithy-client": "npm:^4.9.10" + "@smithy/types": "npm:^4.9.0" "@smithy/util-base64": "npm:^4.3.0" - "@smithy/util-middleware": "npm:^4.2.2" + "@smithy/util-middleware": "npm:^4.2.5" "@smithy/util-utf8": "npm:^4.2.0" tslib: "npm:^2.6.2" - checksum: 10/d667250ebc1bf09c0e227f45b9d5c6036312b014f57c75c7f65165febc3f34d2f176a6d6042231eb3f7682f7a537d3c45f101d8c462026e380fa766a95ed70e6 + checksum: 10/0ce2629d50f15a16e9586060dba2bf8eda4188527b1c753b2f87b687f4f452aa6b26c811c8b364db44692321bed5a359f1bc3ee4666ffe306c568cc58e213c54 languageName: node linkType: hard @@ -1064,16 +1117,16 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/credential-provider-env@npm:3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/credential-provider-env@npm:3.911.0" +"@aws-sdk/credential-provider-env@npm:3.946.0": + version: 3.946.0 + resolution: "@aws-sdk/credential-provider-env@npm:3.946.0" dependencies: - "@aws-sdk/core": "npm:3.911.0" - "@aws-sdk/types": "npm:3.910.0" - "@smithy/property-provider": "npm:^4.2.2" - "@smithy/types": "npm:^4.7.1" + "@aws-sdk/core": "npm:3.946.0" + "@aws-sdk/types": "npm:3.936.0" + "@smithy/property-provider": "npm:^4.2.5" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/520736358644fc27a7f6b72ff2f4feafeef4a6a5ef045980f6399fb056b4b96aa0ca4ebced21c28c07aed6e54abd653a3254c56213842a879be6af975b0500f8 + checksum: 10/743a9bf2e28607a14ed1e816f650c00f068566eaff0db88505e98d4ab4757e00919db0263d272fa46d528202f4f9d48bb0932905e5de00f790fd98963754064e languageName: node linkType: hard @@ -1094,21 +1147,21 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/credential-provider-http@npm:3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/credential-provider-http@npm:3.911.0" +"@aws-sdk/credential-provider-http@npm:3.946.0": + version: 3.946.0 + resolution: "@aws-sdk/credential-provider-http@npm:3.946.0" dependencies: - "@aws-sdk/core": "npm:3.911.0" - "@aws-sdk/types": "npm:3.910.0" - "@smithy/fetch-http-handler": "npm:^5.3.3" - "@smithy/node-http-handler": "npm:^4.4.1" - "@smithy/property-provider": "npm:^4.2.2" - "@smithy/protocol-http": "npm:^5.3.2" - "@smithy/smithy-client": "npm:^4.8.1" - "@smithy/types": "npm:^4.7.1" - "@smithy/util-stream": "npm:^4.5.2" + "@aws-sdk/core": "npm:3.946.0" + "@aws-sdk/types": "npm:3.936.0" + "@smithy/fetch-http-handler": "npm:^5.3.6" + "@smithy/node-http-handler": "npm:^4.4.5" + "@smithy/property-provider": "npm:^4.2.5" + "@smithy/protocol-http": "npm:^5.3.5" + "@smithy/smithy-client": "npm:^4.9.10" + "@smithy/types": "npm:^4.9.0" + "@smithy/util-stream": "npm:^4.5.6" tslib: "npm:^2.6.2" - checksum: 10/2fa71970a2d48873df48dde8aa17e949b0c6e58a3369c16f5f14e2e4193c48bfd6d33ce97c6f4e091d2aafc4ff4c0eb112559286c88749729a17a02bbe479c2d + checksum: 10/c031f544dc581aa82624be0c3427811d3c5f9527c51d6dae1056dc23371a1a747d4090a38dfcda9aefc96ed22f8bfaafa66e7c58dbee49ff5035e0746aab1bee languageName: node linkType: hard @@ -1133,24 +1186,41 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/credential-provider-ini@npm:3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/credential-provider-ini@npm:3.911.0" +"@aws-sdk/credential-provider-ini@npm:3.946.0": + version: 3.946.0 + resolution: "@aws-sdk/credential-provider-ini@npm:3.946.0" dependencies: - "@aws-sdk/core": "npm:3.911.0" - "@aws-sdk/credential-provider-env": "npm:3.911.0" - "@aws-sdk/credential-provider-http": "npm:3.911.0" - "@aws-sdk/credential-provider-process": "npm:3.911.0" - "@aws-sdk/credential-provider-sso": "npm:3.911.0" - "@aws-sdk/credential-provider-web-identity": "npm:3.911.0" - "@aws-sdk/nested-clients": "npm:3.911.0" - "@aws-sdk/types": "npm:3.910.0" - "@smithy/credential-provider-imds": "npm:^4.2.2" - "@smithy/property-provider": "npm:^4.2.2" - "@smithy/shared-ini-file-loader": "npm:^4.3.2" - "@smithy/types": "npm:^4.7.1" + "@aws-sdk/core": "npm:3.946.0" + "@aws-sdk/credential-provider-env": "npm:3.946.0" + "@aws-sdk/credential-provider-http": "npm:3.946.0" + "@aws-sdk/credential-provider-login": "npm:3.946.0" + "@aws-sdk/credential-provider-process": "npm:3.946.0" + "@aws-sdk/credential-provider-sso": "npm:3.946.0" + "@aws-sdk/credential-provider-web-identity": "npm:3.946.0" + "@aws-sdk/nested-clients": "npm:3.946.0" + "@aws-sdk/types": "npm:3.936.0" + "@smithy/credential-provider-imds": "npm:^4.2.5" + "@smithy/property-provider": "npm:^4.2.5" + "@smithy/shared-ini-file-loader": "npm:^4.4.0" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/8cd3f3747e149df49842a22e98ef952cdf730e05be10cd48f9bd21932d009d80d30587c880819174c69350bc7f2ef1929df5bebca76fb5197bb9fcfa7b3f5eb3 + checksum: 10/796b2bea8b544469672c594da106a75a40ca3f6eb244f6c832c6eb51bce7bec6dce62deedf2bee1325f4fab6c6377afd96dfe13f8c7a243464e27a4030551158 + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-login@npm:3.946.0": + version: 3.946.0 + resolution: "@aws-sdk/credential-provider-login@npm:3.946.0" + dependencies: + "@aws-sdk/core": "npm:3.946.0" + "@aws-sdk/nested-clients": "npm:3.946.0" + "@aws-sdk/types": "npm:3.936.0" + "@smithy/property-provider": "npm:^4.2.5" + "@smithy/protocol-http": "npm:^5.3.5" + "@smithy/shared-ini-file-loader": "npm:^4.4.0" + "@smithy/types": "npm:^4.9.0" + tslib: "npm:^2.6.2" + checksum: 10/c6af368cee31813f2fc33d7fe9739f5a6ca5d2501bd9030237cdec1c60d95fe08181224b179d7b6694b5293d0a1b0f4a43a487e5a0caf4f7ad425a7a7104d28e languageName: node linkType: hard @@ -1174,23 +1244,23 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/credential-provider-node@npm:3.911.0, @aws-sdk/credential-provider-node@npm:^3.350.0": - version: 3.911.0 - resolution: "@aws-sdk/credential-provider-node@npm:3.911.0" +"@aws-sdk/credential-provider-node@npm:3.946.0, @aws-sdk/credential-provider-node@npm:^3.350.0": + version: 3.946.0 + resolution: "@aws-sdk/credential-provider-node@npm:3.946.0" dependencies: - "@aws-sdk/credential-provider-env": "npm:3.911.0" - "@aws-sdk/credential-provider-http": "npm:3.911.0" - "@aws-sdk/credential-provider-ini": "npm:3.911.0" - "@aws-sdk/credential-provider-process": "npm:3.911.0" - "@aws-sdk/credential-provider-sso": "npm:3.911.0" - "@aws-sdk/credential-provider-web-identity": "npm:3.911.0" - "@aws-sdk/types": "npm:3.910.0" - "@smithy/credential-provider-imds": "npm:^4.2.2" - "@smithy/property-provider": "npm:^4.2.2" - "@smithy/shared-ini-file-loader": "npm:^4.3.2" - "@smithy/types": "npm:^4.7.1" + "@aws-sdk/credential-provider-env": "npm:3.946.0" + "@aws-sdk/credential-provider-http": "npm:3.946.0" + "@aws-sdk/credential-provider-ini": "npm:3.946.0" + "@aws-sdk/credential-provider-process": "npm:3.946.0" + "@aws-sdk/credential-provider-sso": "npm:3.946.0" + "@aws-sdk/credential-provider-web-identity": "npm:3.946.0" + "@aws-sdk/types": "npm:3.936.0" + "@smithy/credential-provider-imds": "npm:^4.2.5" + "@smithy/property-provider": "npm:^4.2.5" + "@smithy/shared-ini-file-loader": "npm:^4.4.0" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/fafa8408c97e1dd2262593be52de530d8718a4bf4f95b1efbde3f5b8b81a5e96f37bbccafa5216823228d0176665746f9d1810146b1e1f42b766d88ca5a0a083 + checksum: 10/60b08bef3b23af39aa59b44a7d65cfcdf708f04e17215e28fd03673a4a07d04261d8b4e6cf5b4c46b9b13c0ebc9bd999352ef839831595aa280cfa6be292ff6b languageName: node linkType: hard @@ -1207,17 +1277,17 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/credential-provider-process@npm:3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/credential-provider-process@npm:3.911.0" +"@aws-sdk/credential-provider-process@npm:3.946.0": + version: 3.946.0 + resolution: "@aws-sdk/credential-provider-process@npm:3.946.0" dependencies: - "@aws-sdk/core": "npm:3.911.0" - "@aws-sdk/types": "npm:3.910.0" - "@smithy/property-provider": "npm:^4.2.2" - "@smithy/shared-ini-file-loader": "npm:^4.3.2" - "@smithy/types": "npm:^4.7.1" + "@aws-sdk/core": "npm:3.946.0" + "@aws-sdk/types": "npm:3.936.0" + "@smithy/property-provider": "npm:^4.2.5" + "@smithy/shared-ini-file-loader": "npm:^4.4.0" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/73707a2b54f7d4e90a4080ac2cce03aa3738a8684ed0851b874be304839c90b1d8c2167d2eb4d63015e9d79cd19f94f682f3e31f8ecb5c361fd228ebf7f1c726 + checksum: 10/c6bdfc5e182ff26efcf0eaadde48b4be4e301f02bf3e229afd698f41c76675a61fe1e9173e4a0b29b65666dbeb269d4601c649a9e61c9086cb694f50bde2a0bf languageName: node linkType: hard @@ -1236,19 +1306,19 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/credential-provider-sso@npm:3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/credential-provider-sso@npm:3.911.0" +"@aws-sdk/credential-provider-sso@npm:3.946.0": + version: 3.946.0 + resolution: "@aws-sdk/credential-provider-sso@npm:3.946.0" dependencies: - "@aws-sdk/client-sso": "npm:3.911.0" - "@aws-sdk/core": "npm:3.911.0" - "@aws-sdk/token-providers": "npm:3.911.0" - "@aws-sdk/types": "npm:3.910.0" - "@smithy/property-provider": "npm:^4.2.2" - "@smithy/shared-ini-file-loader": "npm:^4.3.2" - "@smithy/types": "npm:^4.7.1" + "@aws-sdk/client-sso": "npm:3.946.0" + "@aws-sdk/core": "npm:3.946.0" + "@aws-sdk/token-providers": "npm:3.946.0" + "@aws-sdk/types": "npm:3.936.0" + "@smithy/property-provider": "npm:^4.2.5" + "@smithy/shared-ini-file-loader": "npm:^4.4.0" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/80d626057b992cdf891fc8eb3365fe45d0071e4024273a95a9699aaeda05751fc492926c230c8c6e99d6bf8dbf4e08a977e5e668326059d2aa936bf1246c1c23 + checksum: 10/a3a1e8bc3ebe3337e6cbff415d1a39cfb1eb7785b4d312d5de3c335a05a090e7150913bcd4c5f9b88da6e9a30bf69a48a739321646850e5650cca4b73d870d1a languageName: node linkType: hard @@ -1266,18 +1336,18 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/credential-provider-web-identity@npm:3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/credential-provider-web-identity@npm:3.911.0" +"@aws-sdk/credential-provider-web-identity@npm:3.946.0": + version: 3.946.0 + resolution: "@aws-sdk/credential-provider-web-identity@npm:3.946.0" dependencies: - "@aws-sdk/core": "npm:3.911.0" - "@aws-sdk/nested-clients": "npm:3.911.0" - "@aws-sdk/types": "npm:3.910.0" - "@smithy/property-provider": "npm:^4.2.2" - "@smithy/shared-ini-file-loader": "npm:^4.3.2" - "@smithy/types": "npm:^4.7.1" + "@aws-sdk/core": "npm:3.946.0" + "@aws-sdk/nested-clients": "npm:3.946.0" + "@aws-sdk/types": "npm:3.936.0" + "@smithy/property-provider": "npm:^4.2.5" + "@smithy/shared-ini-file-loader": "npm:^4.4.0" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/1a67f0799bd44f0cfaf0260c41c8b08009d6df80bf3de1028d67e808e003d11ea474f47d4a5816f147a413b6e29f5e1b28e2f4567b6f7f16ab2cdf196b731895 + checksum: 10/00eca35ed1df538fca1faceae1db6665ba5b22dbfd852f58d089cb211beb6e8ddecd172c3e51edd196063b40684dc1855f1a3fb4f02759652ed73eefee22268e languageName: node linkType: hard @@ -1401,15 +1471,15 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/middleware-host-header@npm:3.910.0": - version: 3.910.0 - resolution: "@aws-sdk/middleware-host-header@npm:3.910.0" +"@aws-sdk/middleware-host-header@npm:3.936.0": + version: 3.936.0 + resolution: "@aws-sdk/middleware-host-header@npm:3.936.0" dependencies: - "@aws-sdk/types": "npm:3.910.0" - "@smithy/protocol-http": "npm:^5.3.2" - "@smithy/types": "npm:^4.7.1" + "@aws-sdk/types": "npm:3.936.0" + "@smithy/protocol-http": "npm:^5.3.5" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/456442bc5ab2b632562203e5f0185da4c9b20129ba7caf8371b021f1f47ac83a06b8726791d9ac350ba60531c9eaff6e6783a4e5be1a5c44fe6a59e766937c84 + checksum: 10/ce707c2402e50b227aa7e22134738ab61e03982ec375926bfa8b074deaeba5aa9296891960294d92aa5010e5446e49224a9d34f7beb55a5ed7b0ea749e49924b languageName: node linkType: hard @@ -1435,14 +1505,14 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/middleware-logger@npm:3.910.0": - version: 3.910.0 - resolution: "@aws-sdk/middleware-logger@npm:3.910.0" +"@aws-sdk/middleware-logger@npm:3.936.0": + version: 3.936.0 + resolution: "@aws-sdk/middleware-logger@npm:3.936.0" dependencies: - "@aws-sdk/types": "npm:3.910.0" - "@smithy/types": "npm:^4.7.1" + "@aws-sdk/types": "npm:3.936.0" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/6153032647a6c5f79d4ef822a52079a6e56f56923d42d8f73b377ad4188df5078424fe21a88706ad27fe30906c602a1fcc68d91955f08ef4f55617bbb41d49e7 + checksum: 10/277d845cdf03aca6d3af548d3751fdb598462fc9877272eadc75fa24e0d27aee267fc84a3583d4116b0fe03c703c59786e56447a88c549bc7394ec8a4ec3d9ea languageName: node linkType: hard @@ -1458,16 +1528,16 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/middleware-recursion-detection@npm:3.910.0": - version: 3.910.0 - resolution: "@aws-sdk/middleware-recursion-detection@npm:3.910.0" +"@aws-sdk/middleware-recursion-detection@npm:3.936.0": + version: 3.936.0 + resolution: "@aws-sdk/middleware-recursion-detection@npm:3.936.0" dependencies: - "@aws-sdk/types": "npm:3.910.0" - "@aws/lambda-invoke-store": "npm:^0.0.1" - "@smithy/protocol-http": "npm:^5.3.2" - "@smithy/types": "npm:^4.7.1" + "@aws-sdk/types": "npm:3.936.0" + "@aws/lambda-invoke-store": "npm:^0.2.0" + "@smithy/protocol-http": "npm:^5.3.5" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/3d727f874667baf1b8076690a17c658bc2260330e23d5fe95bdaa060b235becf1202fb7f3ca277db3530f36e136fbde37dbe2ec78d0a5191d9c5291652a7ae91 + checksum: 10/55fe5db2e8ef0dfcf0e3b37ea0e3640766c44d743f327b7b4dc33d764559908a918edfa4a04a3e04c2e981164998f81a52f747979a28aeed1835a29fd6634a01 languageName: node linkType: hard @@ -1493,25 +1563,25 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/middleware-sdk-s3@npm:3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/middleware-sdk-s3@npm:3.911.0" +"@aws-sdk/middleware-sdk-s3@npm:3.946.0": + version: 3.946.0 + resolution: "@aws-sdk/middleware-sdk-s3@npm:3.946.0" dependencies: - "@aws-sdk/core": "npm:3.911.0" - "@aws-sdk/types": "npm:3.910.0" + "@aws-sdk/core": "npm:3.946.0" + "@aws-sdk/types": "npm:3.936.0" "@aws-sdk/util-arn-parser": "npm:3.893.0" - "@smithy/core": "npm:^3.16.1" - "@smithy/node-config-provider": "npm:^4.3.2" - "@smithy/protocol-http": "npm:^5.3.2" - "@smithy/signature-v4": "npm:^5.3.2" - "@smithy/smithy-client": "npm:^4.8.1" - "@smithy/types": "npm:^4.7.1" + "@smithy/core": "npm:^3.18.7" + "@smithy/node-config-provider": "npm:^4.3.5" + "@smithy/protocol-http": "npm:^5.3.5" + "@smithy/signature-v4": "npm:^5.3.5" + "@smithy/smithy-client": "npm:^4.9.10" + "@smithy/types": "npm:^4.9.0" "@smithy/util-config-provider": "npm:^4.2.0" - "@smithy/util-middleware": "npm:^4.2.2" - "@smithy/util-stream": "npm:^4.5.2" + "@smithy/util-middleware": "npm:^4.2.5" + "@smithy/util-stream": "npm:^4.5.6" "@smithy/util-utf8": "npm:^4.2.0" tslib: "npm:^2.6.2" - checksum: 10/5b154c87e0bbb34fb8d137a0e613cb0b548150f181a44e316e140a3178facdf98cc5d7823c91999a0f26be27db12b23bddbdf380f7c0eae71772749944f1d548 + checksum: 10/97b2ae196c1edfb48eb8b33332d2864053bf7ef152fade966817f9c00f76a5941e48f2224fbbcec07bc5d3f7d3b86833e1978f4b1161480d4863b971d4477876 languageName: node linkType: hard @@ -1563,64 +1633,64 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/middleware-user-agent@npm:3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/middleware-user-agent@npm:3.911.0" +"@aws-sdk/middleware-user-agent@npm:3.946.0": + version: 3.946.0 + resolution: "@aws-sdk/middleware-user-agent@npm:3.946.0" dependencies: - "@aws-sdk/core": "npm:3.911.0" - "@aws-sdk/types": "npm:3.910.0" - "@aws-sdk/util-endpoints": "npm:3.910.0" - "@smithy/core": "npm:^3.16.1" - "@smithy/protocol-http": "npm:^5.3.2" - "@smithy/types": "npm:^4.7.1" + "@aws-sdk/core": "npm:3.946.0" + "@aws-sdk/types": "npm:3.936.0" + "@aws-sdk/util-endpoints": "npm:3.936.0" + "@smithy/core": "npm:^3.18.7" + "@smithy/protocol-http": "npm:^5.3.5" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/c6f2ea464bdc2cb084243ec0c4edf39e55e35ae47ac1c6baa7349f80c87ad8efda1b84587be4c75f21347e52229e82d41c3ac0ae48aaa6a71555f1c83bbdec1c + checksum: 10/db44fc55412e6d7d1fd6dfa356b44321f157961afd830956bd040233e453243a4d60e410edb9d244972e38f365ff0dd0798a339d1814d6322a3838c0680ac950 languageName: node linkType: hard -"@aws-sdk/nested-clients@npm:3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/nested-clients@npm:3.911.0" +"@aws-sdk/nested-clients@npm:3.946.0": + version: 3.946.0 + resolution: "@aws-sdk/nested-clients@npm:3.946.0" dependencies: "@aws-crypto/sha256-browser": "npm:5.2.0" "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/core": "npm:3.911.0" - "@aws-sdk/middleware-host-header": "npm:3.910.0" - "@aws-sdk/middleware-logger": "npm:3.910.0" - "@aws-sdk/middleware-recursion-detection": "npm:3.910.0" - "@aws-sdk/middleware-user-agent": "npm:3.911.0" - "@aws-sdk/region-config-resolver": "npm:3.910.0" - "@aws-sdk/types": "npm:3.910.0" - "@aws-sdk/util-endpoints": "npm:3.910.0" - "@aws-sdk/util-user-agent-browser": "npm:3.910.0" - "@aws-sdk/util-user-agent-node": "npm:3.911.0" - "@smithy/config-resolver": "npm:^4.3.2" - "@smithy/core": "npm:^3.16.1" - "@smithy/fetch-http-handler": "npm:^5.3.3" - "@smithy/hash-node": "npm:^4.2.2" - "@smithy/invalid-dependency": "npm:^4.2.2" - "@smithy/middleware-content-length": "npm:^4.2.2" - "@smithy/middleware-endpoint": "npm:^4.3.3" - "@smithy/middleware-retry": "npm:^4.4.3" - "@smithy/middleware-serde": "npm:^4.2.2" - "@smithy/middleware-stack": "npm:^4.2.2" - "@smithy/node-config-provider": "npm:^4.3.2" - "@smithy/node-http-handler": "npm:^4.4.1" - "@smithy/protocol-http": "npm:^5.3.2" - "@smithy/smithy-client": "npm:^4.8.1" - "@smithy/types": "npm:^4.7.1" - "@smithy/url-parser": "npm:^4.2.2" + "@aws-sdk/core": "npm:3.946.0" + "@aws-sdk/middleware-host-header": "npm:3.936.0" + "@aws-sdk/middleware-logger": "npm:3.936.0" + "@aws-sdk/middleware-recursion-detection": "npm:3.936.0" + "@aws-sdk/middleware-user-agent": "npm:3.946.0" + "@aws-sdk/region-config-resolver": "npm:3.936.0" + "@aws-sdk/types": "npm:3.936.0" + "@aws-sdk/util-endpoints": "npm:3.936.0" + "@aws-sdk/util-user-agent-browser": "npm:3.936.0" + "@aws-sdk/util-user-agent-node": "npm:3.946.0" + "@smithy/config-resolver": "npm:^4.4.3" + "@smithy/core": "npm:^3.18.7" + "@smithy/fetch-http-handler": "npm:^5.3.6" + "@smithy/hash-node": "npm:^4.2.5" + "@smithy/invalid-dependency": "npm:^4.2.5" + "@smithy/middleware-content-length": "npm:^4.2.5" + "@smithy/middleware-endpoint": "npm:^4.3.14" + "@smithy/middleware-retry": "npm:^4.4.14" + "@smithy/middleware-serde": "npm:^4.2.6" + "@smithy/middleware-stack": "npm:^4.2.5" + "@smithy/node-config-provider": "npm:^4.3.5" + "@smithy/node-http-handler": "npm:^4.4.5" + "@smithy/protocol-http": "npm:^5.3.5" + "@smithy/smithy-client": "npm:^4.9.10" + "@smithy/types": "npm:^4.9.0" + "@smithy/url-parser": "npm:^4.2.5" "@smithy/util-base64": "npm:^4.3.0" "@smithy/util-body-length-browser": "npm:^4.2.0" "@smithy/util-body-length-node": "npm:^4.2.1" - "@smithy/util-defaults-mode-browser": "npm:^4.3.2" - "@smithy/util-defaults-mode-node": "npm:^4.2.3" - "@smithy/util-endpoints": "npm:^3.2.2" - "@smithy/util-middleware": "npm:^4.2.2" - "@smithy/util-retry": "npm:^4.2.2" + "@smithy/util-defaults-mode-browser": "npm:^4.3.13" + "@smithy/util-defaults-mode-node": "npm:^4.2.16" + "@smithy/util-endpoints": "npm:^3.2.5" + "@smithy/util-middleware": "npm:^4.2.5" + "@smithy/util-retry": "npm:^4.2.5" "@smithy/util-utf8": "npm:^4.2.0" tslib: "npm:^2.6.2" - checksum: 10/c666f08f4d9f3359b29852f64c1fcd1a837002cf546554e44ca23a4f2ba67ccfa356aae3e49d531b3481d1f9b982d61ad78e6a4779fab661aa0dfc522a90fd61 + checksum: 10/137089ad8cdca5e864e1643acfea6305d9723d1dd4e842fc37693374391c8ce8575b4e23757c2939530e9cf4bec2821e30e1ef18abb73d6d0d8c23880658f6cc languageName: node linkType: hard @@ -1682,17 +1752,16 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/region-config-resolver@npm:3.910.0": - version: 3.910.0 - resolution: "@aws-sdk/region-config-resolver@npm:3.910.0" +"@aws-sdk/region-config-resolver@npm:3.936.0": + version: 3.936.0 + resolution: "@aws-sdk/region-config-resolver@npm:3.936.0" dependencies: - "@aws-sdk/types": "npm:3.910.0" - "@smithy/node-config-provider": "npm:^4.3.2" - "@smithy/types": "npm:^4.7.1" - "@smithy/util-config-provider": "npm:^4.2.0" - "@smithy/util-middleware": "npm:^4.2.2" + "@aws-sdk/types": "npm:3.936.0" + "@smithy/config-resolver": "npm:^4.4.3" + "@smithy/node-config-provider": "npm:^4.3.5" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/30c72ad18fa9440765ace4c4acfa26f6525573adff1fb9c12b251da709413a6e0d08894c62d011b463dfd414ead54095845c7225d2a2654adf3e5871743ac965 + checksum: 10/384ffaba2aacb86987768a2208d29f8495322ba4ccb8e536d2aeeea7dbc92ac35bf624b9e15dee63430113f7e341d9f155ca9f72ae96bf2436616a5705f790f2 languageName: node linkType: hard @@ -1710,17 +1779,17 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/signature-v4-multi-region@npm:3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/signature-v4-multi-region@npm:3.911.0" +"@aws-sdk/signature-v4-multi-region@npm:3.946.0": + version: 3.946.0 + resolution: "@aws-sdk/signature-v4-multi-region@npm:3.946.0" dependencies: - "@aws-sdk/middleware-sdk-s3": "npm:3.911.0" - "@aws-sdk/types": "npm:3.910.0" - "@smithy/protocol-http": "npm:^5.3.2" - "@smithy/signature-v4": "npm:^5.3.2" - "@smithy/types": "npm:^4.7.1" + "@aws-sdk/middleware-sdk-s3": "npm:3.946.0" + "@aws-sdk/types": "npm:3.936.0" + "@smithy/protocol-http": "npm:^5.3.5" + "@smithy/signature-v4": "npm:^5.3.5" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/da232ca896b90c6a8ae1370e29adb83af67a5ca4b660b61ba98ade2a31b173e5a31f4f535f342d5e4bac7f963641f4401c22616feca4dc622c538ef36982fa95 + checksum: 10/a76e1fb93e7b86710d2aec3a6c97761681b2e5f25358b8b04fe0ccf49b0ae159647c8159305294e4d1a953b1514851782c3b35b56f00a7c41c6f8fe1c3d6834c languageName: node linkType: hard @@ -1739,18 +1808,18 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/token-providers@npm:3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/token-providers@npm:3.911.0" +"@aws-sdk/token-providers@npm:3.946.0": + version: 3.946.0 + resolution: "@aws-sdk/token-providers@npm:3.946.0" dependencies: - "@aws-sdk/core": "npm:3.911.0" - "@aws-sdk/nested-clients": "npm:3.911.0" - "@aws-sdk/types": "npm:3.910.0" - "@smithy/property-provider": "npm:^4.2.2" - "@smithy/shared-ini-file-loader": "npm:^4.3.2" - "@smithy/types": "npm:^4.7.1" + "@aws-sdk/core": "npm:3.946.0" + "@aws-sdk/nested-clients": "npm:3.946.0" + "@aws-sdk/types": "npm:3.936.0" + "@smithy/property-provider": "npm:^4.2.5" + "@smithy/shared-ini-file-loader": "npm:^4.4.0" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/88cdadde7dbddc88c5cf33a519f7dd108ce74254acdee6a6c3e4c6aa931350f873c37abe426f43238ae58b02ce9dd7bc33c8b214813938eb9db88e74172f484b + checksum: 10/8c9e60a94bda6b9152e843f45ac6a53fc91b825eaa5ae3061aec12391d1abd561184d681eb71fc0c3c84fce7ea43b53d1ab13510c21d72b345361d734b47f988 languageName: node linkType: hard @@ -1774,13 +1843,13 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/types@npm:3.910.0, @aws-sdk/types@npm:^3.222.0, @aws-sdk/types@npm:^3.347.0": - version: 3.910.0 - resolution: "@aws-sdk/types@npm:3.910.0" +"@aws-sdk/types@npm:3.936.0, @aws-sdk/types@npm:^3.222.0, @aws-sdk/types@npm:^3.347.0": + version: 3.936.0 + resolution: "@aws-sdk/types@npm:3.936.0" dependencies: - "@smithy/types": "npm:^4.7.1" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/7865602184322eaa0588ae16ac3cda941d8aaceaa88a6c2ab723e37dc4017db84ba5cfdf6d3b42b01d132d3090c08a732598bf9d9734b66f1c82903e1602af6e + checksum: 10/a8d11e5c88c0006962f7fb6dd37a7cab38ee5e270ffc8046c27a19b709179e1744b173e5538599f3c2326301549fbd18ab473c7dd018f8b49cff1e9c201ccf03 languageName: node linkType: hard @@ -1835,16 +1904,16 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/util-endpoints@npm:3.910.0": - version: 3.910.0 - resolution: "@aws-sdk/util-endpoints@npm:3.910.0" +"@aws-sdk/util-endpoints@npm:3.936.0": + version: 3.936.0 + resolution: "@aws-sdk/util-endpoints@npm:3.936.0" dependencies: - "@aws-sdk/types": "npm:3.910.0" - "@smithy/types": "npm:^4.7.1" - "@smithy/url-parser": "npm:^4.2.2" - "@smithy/util-endpoints": "npm:^3.2.2" + "@aws-sdk/types": "npm:3.936.0" + "@smithy/types": "npm:^4.9.0" + "@smithy/url-parser": "npm:^4.2.5" + "@smithy/util-endpoints": "npm:^3.2.5" tslib: "npm:^2.6.2" - checksum: 10/c84a4f60cffad6db765e1f2a6e9a1d178adae1023f9c0594ed36504cfe9b9438073a4e81f668a1c9b27298ceb74800ff15088fa0d757cbddad41a179a160f879 + checksum: 10/fd9d995cd79886df424a8aea9407d9f810ea1a28af0d1f31f63310edc5839e086732479932d470404376dd5aa5672be1210cb62b9ce3942da29c0461215b94f2 languageName: node linkType: hard @@ -1899,15 +1968,15 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/util-user-agent-browser@npm:3.910.0": - version: 3.910.0 - resolution: "@aws-sdk/util-user-agent-browser@npm:3.910.0" +"@aws-sdk/util-user-agent-browser@npm:3.936.0": + version: 3.936.0 + resolution: "@aws-sdk/util-user-agent-browser@npm:3.936.0" dependencies: - "@aws-sdk/types": "npm:3.910.0" - "@smithy/types": "npm:^4.7.1" + "@aws-sdk/types": "npm:3.936.0" + "@smithy/types": "npm:^4.9.0" bowser: "npm:^2.11.0" tslib: "npm:^2.6.2" - checksum: 10/0bd5becebf9de4c3a02f88be5cb1912fb2b82f8ecf17e82840dcacfeac96fef4f04188645fe5f4cc6ecdbdc67dc5c408bb9843a9a534afe6be68af39ae1e9440 + checksum: 10/3b08066300dfbe202ba510d547ec4988c565e0cc103c726d452696942e5c0a66dfe2b271398ee6d46e8d3da546f094d491b74f8aa91ce3ba6553e5c6802a650e languageName: node linkType: hard @@ -1928,21 +1997,21 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/util-user-agent-node@npm:3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/util-user-agent-node@npm:3.911.0" +"@aws-sdk/util-user-agent-node@npm:3.946.0": + version: 3.946.0 + resolution: "@aws-sdk/util-user-agent-node@npm:3.946.0" dependencies: - "@aws-sdk/middleware-user-agent": "npm:3.911.0" - "@aws-sdk/types": "npm:3.910.0" - "@smithy/node-config-provider": "npm:^4.3.2" - "@smithy/types": "npm:^4.7.1" + "@aws-sdk/middleware-user-agent": "npm:3.946.0" + "@aws-sdk/types": "npm:3.936.0" + "@smithy/node-config-provider": "npm:^4.3.5" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" peerDependencies: aws-crt: ">=1.0.0" peerDependenciesMeta: aws-crt: optional: true - checksum: 10/c87cd348edc6436f0788eaad4b05e15c4f2fe8f096f9e90d40c13f440241da4336192190be2720f9941f1c72ddfd1d8e317d616fe5db1b6d3780fafadc7941e4 + checksum: 10/7f81c001abb8ea7fc8da9f342875082fa5f0c3c069b102a3fdece8e37e9560230c0f30056cf27dd4bdc47b8a5b2cc2d127c2029bf264fdb980f77729f8cee032 languageName: node linkType: hard @@ -1956,21 +2025,21 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/xml-builder@npm:3.911.0": - version: 3.911.0 - resolution: "@aws-sdk/xml-builder@npm:3.911.0" +"@aws-sdk/xml-builder@npm:3.930.0": + version: 3.930.0 + resolution: "@aws-sdk/xml-builder@npm:3.930.0" dependencies: - "@smithy/types": "npm:^4.7.1" + "@smithy/types": "npm:^4.9.0" fast-xml-parser: "npm:5.2.5" tslib: "npm:^2.6.2" - checksum: 10/0208021abe8ac9633c3d13f1b108de58496af152029e6b0e10fe621a1dabfa412b1f14c91d531aeaf5431335b278d354a07d96acb8bc30369cff591d073338d8 + checksum: 10/7956588f54e282c0b6cefaeefec6103c0cf787427f29d599330241b5349635f0cc3742df0a3aa6d1792b237178e3cfa3ee156379aa160fb8d7bd854c4608263d languageName: node linkType: hard -"@aws/lambda-invoke-store@npm:^0.0.1": - version: 0.0.1 - resolution: "@aws/lambda-invoke-store@npm:0.0.1" - checksum: 10/e8f54d28aade8828962f2871a22aa4e960ebc40c8fa551414181dd9dd32d6258279013c42f88e57d17aa4252cb5ed00df6a49fc35185f9fa6b6f351ccf821bd6 +"@aws/lambda-invoke-store@npm:^0.2.0": + version: 0.2.2 + resolution: "@aws/lambda-invoke-store@npm:0.2.2" + checksum: 10/18cd0cec90d9d865c9089218ef2220b0a7302a860c9a3f808b101386f569abc5ee11eb98a36947bed280a63308dd5df23c39e7b07fe9ac4f4ffcd0c4dce537c4 languageName: node linkType: hard @@ -2222,7 +2291,7 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.16.0, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.24.2, @babel/code-frame@npm:^7.27.1, @babel/code-frame@npm:^7.8.3": +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.16.0, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.27.1, @babel/code-frame@npm:^7.8.3": version: 7.27.1 resolution: "@babel/code-frame@npm:7.27.1" dependencies: @@ -2240,39 +2309,39 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.24.7, @babel/core@npm:^7.28.0": - version: 7.28.4 - resolution: "@babel/core@npm:7.28.4" +"@babel/core@npm:^7.23.9, @babel/core@npm:^7.24.7, @babel/core@npm:^7.27.4, @babel/core@npm:^7.28.0": + version: 7.28.5 + resolution: "@babel/core@npm:7.28.5" dependencies: "@babel/code-frame": "npm:^7.27.1" - "@babel/generator": "npm:^7.28.3" + "@babel/generator": "npm:^7.28.5" "@babel/helper-compilation-targets": "npm:^7.27.2" "@babel/helper-module-transforms": "npm:^7.28.3" "@babel/helpers": "npm:^7.28.4" - "@babel/parser": "npm:^7.28.4" + "@babel/parser": "npm:^7.28.5" "@babel/template": "npm:^7.27.2" - "@babel/traverse": "npm:^7.28.4" - "@babel/types": "npm:^7.28.4" + "@babel/traverse": "npm:^7.28.5" + "@babel/types": "npm:^7.28.5" "@jridgewell/remapping": "npm:^2.3.5" convert-source-map: "npm:^2.0.0" debug: "npm:^4.1.0" gensync: "npm:^1.0.0-beta.2" json5: "npm:^2.2.3" semver: "npm:^6.3.1" - checksum: 10/0593295241fac9be567145ef16f3858d34fc91390a9438c6d47476be9823af4cc0488c851c59702dd46b968e9fd46d17ddf0105ea30195ca85f5a66b4044c519 + checksum: 10/2f1e224125179f423f4300d605a0c5a3ef315003281a63b1744405b2605ee2a2ffc5b1a8349aa4f262c72eca31c7e1802377ee04ad2b852a2c88f8ace6cac324 languageName: node linkType: hard -"@babel/generator@npm:^7.28.3, @babel/generator@npm:^7.7.2": - version: 7.28.3 - resolution: "@babel/generator@npm:7.28.3" +"@babel/generator@npm:^7.27.5, @babel/generator@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/generator@npm:7.28.5" dependencies: - "@babel/parser": "npm:^7.28.3" - "@babel/types": "npm:^7.28.2" + "@babel/parser": "npm:^7.28.5" + "@babel/types": "npm:^7.28.5" "@jridgewell/gen-mapping": "npm:^0.3.12" "@jridgewell/trace-mapping": "npm:^0.3.28" jsesc: "npm:^3.0.2" - checksum: 10/d00d1e6b51059e47594aab7920b88ec6fcef6489954a9172235ab57ad2e91b39c95376963a6e2e4cc7e8b88fa4f931018f71f9ab32bbc9c0bc0de35a0231f26c + checksum: 10/ae618f0a17a6d76c3983e1fd5d9c2f5fdc07703a119efdb813a7d9b8ad4be0a07d4c6f0d718440d2de01a68e321f64e2d63c77fc5d43ae47ae143746ef28ac1f languageName: node linkType: hard @@ -2364,10 +2433,10 @@ __metadata: languageName: node linkType: hard -"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.24.7, @babel/helper-plugin-utils@npm:^7.24.8, @babel/helper-plugin-utils@npm:^7.25.9, @babel/helper-plugin-utils@npm:^7.8.0": - version: 7.25.9 - resolution: "@babel/helper-plugin-utils@npm:7.25.9" - checksum: 10/e347d87728b1ab10b6976d46403941c8f9008c045ea6d99997a7ffca7b852dc34b6171380f7b17edf94410e0857ff26f3a53d8618f11d73744db86e8ca9b8c64 +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.24.7, @babel/helper-plugin-utils@npm:^7.24.8, @babel/helper-plugin-utils@npm:^7.27.1, @babel/helper-plugin-utils@npm:^7.8.0": + version: 7.27.1 + resolution: "@babel/helper-plugin-utils@npm:7.27.1" + checksum: 10/96136c2428888e620e2ec493c25888f9ceb4a21099dcf3dd4508ea64b58cdedbd5a9fb6c7b352546de84d6c24edafe482318646932a22c449ebd16d16c22d864 languageName: node linkType: hard @@ -2411,10 +2480,10 @@ __metadata: languageName: node linkType: hard -"@babel/helper-validator-identifier@npm:^7.24.7, @babel/helper-validator-identifier@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/helper-validator-identifier@npm:7.27.1" - checksum: 10/75041904d21bdc0cd3b07a8ac90b11d64cd3c881e89cb936fa80edd734bf23c35e6bd1312611e8574c4eab1f3af0f63e8a5894f4699e9cfdf70c06fcf4252320 +"@babel/helper-validator-identifier@npm:^7.24.7, @babel/helper-validator-identifier@npm:^7.27.1, @babel/helper-validator-identifier@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/helper-validator-identifier@npm:7.28.5" + checksum: 10/8e5d9b0133702cfacc7f368bf792f0f8ac0483794877c6dca5fcb73810ee138e27527701826fb58a40a004f3a5ec0a2f3c3dd5e326d262530b119918f3132ba7 languageName: node linkType: hard @@ -2447,14 +2516,14 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.24.7, @babel/parser@npm:^7.26.7, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.27.5, @babel/parser@npm:^7.28.3, @babel/parser@npm:^7.28.4": - version: 7.28.4 - resolution: "@babel/parser@npm:7.28.4" +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.24.7, @babel/parser@npm:^7.26.7, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.27.5, @babel/parser@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/parser@npm:7.28.5" dependencies: - "@babel/types": "npm:^7.28.4" + "@babel/types": "npm:^7.28.5" bin: parser: ./bin/babel-parser.js - checksum: 10/f54c46213ef180b149f6a17ea765bf40acc1aebe2009f594e2a283aec69a190c6dda1fdf24c61a258dbeb903abb8ffb7a28f1a378f8ab5d333846ce7b7e23bf1 + checksum: 10/8d9bfb437af6c97a7f6351840b9ac06b4529ba79d6d3def24d6c2996ab38ff7f1f9d301e868ca84a93a3050fadb3d09dbc5105b24634cd281671ac11eebe8df7 languageName: node linkType: hard @@ -2480,7 +2549,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-class-properties@npm:^7.8.3": +"@babel/plugin-syntax-class-properties@npm:^7.12.13": version: 7.12.13 resolution: "@babel/plugin-syntax-class-properties@npm:7.12.13" dependencies: @@ -2491,6 +2560,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-syntax-class-static-block@npm:^7.14.5": + version: 7.14.5 + resolution: "@babel/plugin-syntax-class-static-block@npm:7.14.5" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.14.5" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/3e80814b5b6d4fe17826093918680a351c2d34398a914ce6e55d8083d72a9bdde4fbaf6a2dcea0e23a03de26dc2917ae3efd603d27099e2b98380345703bf948 + languageName: node + linkType: hard + "@babel/plugin-syntax-flow@npm:^7.24.7": version: 7.24.7 resolution: "@babel/plugin-syntax-flow@npm:7.24.7" @@ -2502,7 +2582,18 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-import-meta@npm:^7.8.3": +"@babel/plugin-syntax-import-attributes@npm:^7.24.7": + version: 7.27.1 + resolution: "@babel/plugin-syntax-import-attributes@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/97973982fff1bbf86b3d1df13380567042887c50e2ae13a400d02a8ff2c9742a60a75e279bfb73019e1cd9710f04be5e6ab81f896e6678dcfcec8b135e8896cf + languageName: node + linkType: hard + +"@babel/plugin-syntax-import-meta@npm:^7.10.4": version: 7.10.4 resolution: "@babel/plugin-syntax-import-meta@npm:7.10.4" dependencies: @@ -2524,18 +2615,18 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-jsx@npm:^7.24.7, @babel/plugin-syntax-jsx@npm:^7.7.2": - version: 7.24.7 - resolution: "@babel/plugin-syntax-jsx@npm:7.24.7" +"@babel/plugin-syntax-jsx@npm:^7.24.7, @babel/plugin-syntax-jsx@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-syntax-jsx@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/a93516ae5b34868ab892a95315027d4e5e38e8bd1cfca6158f2974b0901cbb32bbe64ea10ad5b25f919ddc40c6d8113c4823372909c9c9922170c12b0b1acecb + checksum: 10/c6d1324cff286a369aa95d99b8abd21dd07821b5d3affd5fe7d6058c84cff9190743287826463ee57a7beecd10fa1e4bc99061df532ee14e188c1c8937b13e3a languageName: node linkType: hard -"@babel/plugin-syntax-logical-assignment-operators@npm:^7.8.3": +"@babel/plugin-syntax-logical-assignment-operators@npm:^7.10.4": version: 7.10.4 resolution: "@babel/plugin-syntax-logical-assignment-operators@npm:7.10.4" dependencies: @@ -2557,7 +2648,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-numeric-separator@npm:^7.8.3": +"@babel/plugin-syntax-numeric-separator@npm:^7.10.4": version: 7.10.4 resolution: "@babel/plugin-syntax-numeric-separator@npm:7.10.4" dependencies: @@ -2601,7 +2692,18 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-top-level-await@npm:^7.8.3": +"@babel/plugin-syntax-private-property-in-object@npm:^7.14.5": + version: 7.14.5 + resolution: "@babel/plugin-syntax-private-property-in-object@npm:7.14.5" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.14.5" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10/b317174783e6e96029b743ccff2a67d63d38756876e7e5d0ba53a322e38d9ca452c13354a57de1ad476b4c066dbae699e0ca157441da611117a47af88985ecda + languageName: node + linkType: hard + +"@babel/plugin-syntax-top-level-await@npm:^7.14.5": version: 7.14.5 resolution: "@babel/plugin-syntax-top-level-await@npm:7.14.5" dependencies: @@ -2612,14 +2714,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-typescript@npm:^7.24.7, @babel/plugin-syntax-typescript@npm:^7.7.2": - version: 7.25.9 - resolution: "@babel/plugin-syntax-typescript@npm:7.25.9" +"@babel/plugin-syntax-typescript@npm:^7.24.7, @babel/plugin-syntax-typescript@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-syntax-typescript@npm:7.27.1" dependencies: - "@babel/helper-plugin-utils": "npm:^7.25.9" + "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/0e9821e8ba7d660c36c919654e4144a70546942ae184e85b8102f2322451eae102cbfadbcadd52ce077a2b44b400ee52394c616feab7b5b9f791b910e933fd33 + checksum: 10/87836f7e32af624c2914c73cd6b9803cf324e07d43f61dbb973c6a86f75df725e12540d91fac7141c14b697aa9268fd064220998daced156e96ac3062d7afb41 languageName: node linkType: hard @@ -2764,14 +2866,14 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.26.0, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.0, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.26.0, @babel/runtime@npm:^7.28.4, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.0, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": version: 7.28.4 resolution: "@babel/runtime@npm:7.28.4" checksum: 10/6c9a70452322ea80b3c9b2a412bcf60771819213a67576c8cec41e88a95bb7bf01fc983754cda35dc19603eef52df22203ccbf7777b9d6316932f9fb77c25163 languageName: node linkType: hard -"@babel/template@npm:^7.27.2, @babel/template@npm:^7.3.3": +"@babel/template@npm:^7.27.2": version: 7.27.2 resolution: "@babel/template@npm:7.27.2" dependencies: @@ -2782,35 +2884,35 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.24.8, @babel/traverse@npm:^7.25.0, @babel/traverse@npm:^7.25.4, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.4, @babel/traverse@npm:^7.4.5": - version: 7.28.4 - resolution: "@babel/traverse@npm:7.28.4" +"@babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.24.8, @babel/traverse@npm:^7.25.0, @babel/traverse@npm:^7.25.4, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.5, @babel/traverse@npm:^7.4.5": + version: 7.28.5 + resolution: "@babel/traverse@npm:7.28.5" dependencies: "@babel/code-frame": "npm:^7.27.1" - "@babel/generator": "npm:^7.28.3" + "@babel/generator": "npm:^7.28.5" "@babel/helper-globals": "npm:^7.28.0" - "@babel/parser": "npm:^7.28.4" + "@babel/parser": "npm:^7.28.5" "@babel/template": "npm:^7.27.2" - "@babel/types": "npm:^7.28.4" + "@babel/types": "npm:^7.28.5" debug: "npm:^4.3.1" - checksum: 10/c3099364b7b1c36bcd111099195d4abeef16499e5defb1e56766b754e8b768c252e856ed9041665158aa1b31215fc6682632756803c8fa53405381ec08c4752b + checksum: 10/1fce426f5ea494913c40f33298ce219708e703f71cac7ac045ebde64b5a7b17b9275dfa4e05fb92c3f123136913dff62c8113172f4a5de66dab566123dbe7437 languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.27.1, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.4, @babel/types@npm:^7.3.3": - version: 7.28.4 - resolution: "@babel/types@npm:7.28.4" +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.4, @babel/types@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/types@npm:7.28.5" dependencies: "@babel/helper-string-parser": "npm:^7.27.1" - "@babel/helper-validator-identifier": "npm:^7.27.1" - checksum: 10/db50bf257aafa5d845ad16dae0587f57d596e4be4cbb233ea539976a4c461f9fbcc0bf3d37adae3f8ce5dcb4001462aa608f3558161258b585f6ce6ce21a2e45 + "@babel/helper-validator-identifier": "npm:^7.28.5" + checksum: 10/4256bb9fb2298c4f9b320bde56e625b7091ea8d2433d98dcf524d4086150da0b6555aabd7d0725162670614a9ac5bf036d1134ca13dedc9707f988670f1362d7 languageName: node linkType: hard -"@backstage-community/plugin-explore-common@npm:^0.5.0": - version: 0.5.0 - resolution: "@backstage-community/plugin-explore-common@npm:0.5.0" - checksum: 10/258dd4102f64220b28803db3825e52bb2974cad5704ca134a75b7924dd95a601c92fd8d6606cc3bcd949feace420a407a85ff32f4bd782c8e45ab728b62cda46 +"@backstage-community/plugin-explore-common@npm:^0.9.0": + version: 0.9.0 + resolution: "@backstage-community/plugin-explore-common@npm:0.9.0" + checksum: 10/ff0a81ab775a415fa3ec088cc47dd79836e50c653414ec7ff9ba4b7022eb3327ef9d47fb572aafeb4da6deaff86ac1cbd09a62caf84849e4027f30764be632d3 languageName: node linkType: hard @@ -2891,7 +2993,7 @@ __metadata: "@manypkg/get-packages": "npm:^1.1.3" "@octokit/rest": "npm:^19.0.3" "@opentelemetry/api": "npm:^1.9.0" - "@types/archiver": "npm:^6.0.0" + "@types/archiver": "npm:^7.0.0" "@types/base64-stream": "npm:^1.0.2" "@types/compression": "npm:^1.7.5" "@types/concat-stream": "npm:^2.0.0" @@ -2911,10 +3013,11 @@ __metadata: cookie: "npm:^0.7.0" cors: "npm:^2.8.5" cron: "npm:^3.0.0" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" express-rate-limit: "npm:^7.5.0" fs-extra: "npm:^11.2.0" + get-port: "npm:^5.1.1" git-url-parse: "npm:^15.0.0" helmet: "npm:^6.0.0" http-errors: "npm:^2.0.0" @@ -2930,7 +3033,7 @@ __metadata: msw: "npm:^1.0.0" mysql2: "npm:^3.0.0" node-fetch: "npm:^2.7.0" - node-forge: "npm:^1.3.1" + node-forge: "npm:^1.3.2" node-mocks-http: "npm:^1.0.0" p-limit: "npm:^3.1.0" path-to-regexp: "npm:^8.0.0" @@ -2953,9 +3056,12 @@ __metadata: zod-to-json-schema: "npm:^3.20.4" peerDependencies: "@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 languageName: unknown linkType: soft @@ -2999,7 +3105,7 @@ __metadata: "@module-federation/sdk": "npm:^0.9.0" "@types/express": "npm:^4.17.6" chokidar: "npm:^3.5.3" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" fs-extra: "npm:^11.2.0" lodash: "npm:^4.17.21" @@ -3022,7 +3128,7 @@ __metadata: "@types/express": "npm:^4.17.6" "@types/express-serve-static-core": "npm:^4.17.5" ajv: "npm:^8.16.0" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-openapi-validator: "npm:^5.5.8" express-promise-router: "npm:^4.1.0" get-port: "npm:^5.1.1" @@ -3085,7 +3191,7 @@ __metadata: "@types/supertest": "npm:^2.0.8" better-sqlite3: "npm:^12.0.0" cookie: "npm:^0.7.0" - express: "npm:^4.17.1" + express: "npm:^4.22.0" fs-extra: "npm:^11.0.0" keyv: "npm:^5.2.1" knex: "npm:^3.0.0" @@ -3094,8 +3200,8 @@ __metadata: pg: "npm:^8.11.3" pg-connection-string: "npm:^2.3.0" supertest: "npm:^7.0.0" - testcontainers: "npm:^10.0.0" - textextensions: "npm:^5.16.0" + testcontainers: "npm:^11.9.0" + text-extensions: "npm:^2.4.0" uuid: "npm:^11.0.0" yn: "npm:^4.0.0" zod: "npm:^3.22.4" @@ -3137,7 +3243,12 @@ __metadata: resolution: "@backstage/cli-common@workspace:packages/cli-common" dependencies: "@backstage/cli": "workspace:^" - "@types/node": "npm:^20.16.0" + "@backstage/errors": "workspace:^" + "@types/cross-spawn": "npm:^6.0.2" + "@types/node": "npm:^22.13.14" + cross-spawn: "npm:^7.0.3" + global-agent: "npm:^3.0.0" + undici: "npm:^7.2.3" languageName: unknown linkType: soft @@ -3187,10 +3298,11 @@ __metadata: "@backstage/test-utils": "workspace:^" "@backstage/theme": "workspace:^" "@backstage/types": "workspace:^" + "@jest/environment-jsdom-abstract": "npm:^30.0.0" "@manypkg/get-packages": "npm:^1.1.3" "@module-federation/enhanced": "npm:^0.9.0" "@octokit/request": "npm:^8.0.0" - "@pmmmwh/react-refresh-webpack-plugin": "npm:^0.5.7" + "@pmmmwh/react-refresh-webpack-plugin": "npm:^0.6.0" "@rollup/plugin-commonjs": "npm:^26.0.0" "@rollup/plugin-json": "npm:^6.0.0" "@rollup/plugin-node-resolve": "npm:^15.0.0" @@ -3201,17 +3313,17 @@ __metadata: "@spotify/eslint-config-base": "npm:^15.0.0" "@spotify/eslint-config-react": "npm:^15.0.0" "@spotify/eslint-config-typescript": "npm:^15.0.0" - "@swc/core": "npm:^1.3.46" - "@swc/helpers": "npm:^0.5.0" - "@swc/jest": "npm:^0.2.22" + "@swc/core": "npm:^1.15.6" + "@swc/helpers": "npm:^0.5.17" + "@swc/jest": "npm:^0.2.39" "@types/cross-spawn": "npm:^6.0.2" "@types/ejs": "npm:^3.1.3" "@types/express": "npm:^4.17.6" "@types/fs-extra": "npm:^11.0.0" "@types/http-proxy": "npm:^1.17.4" "@types/inquirer": "npm:^8.1.3" - "@types/jest": "npm:^29.5.11" - "@types/node": "npm:^20.16.0" + "@types/jest": "npm:^30.0.0" + "@types/node": "npm:^22.13.14" "@types/npm-packlist": "npm:^3.0.0" "@types/recursive-readdir": "npm:^2.2.0" "@types/rollup-plugin-peer-deps-external": "npm:^2.2.0" @@ -3236,7 +3348,7 @@ __metadata: css-loader: "npm:^6.5.1" ctrlc-windows: "npm:^2.1.0" del: "npm:^8.0.0" - esbuild: "npm:^0.25.0" + esbuild: "npm:^0.27.0" esbuild-loader: "npm:^4.0.0" eslint: "npm:^8.6.0" eslint-config-prettier: "npm:^9.0.0" @@ -3250,7 +3362,7 @@ __metadata: eslint-plugin-unused-imports: "npm:^4.1.4" eslint-rspack-plugin: "npm:^4.2.1" eslint-webpack-plugin: "npm:^4.2.0" - express: "npm:^4.17.1" + express: "npm:^4.22.0" fork-ts-checker-webpack-plugin: "npm:^9.0.0" fs-extra: "npm:^11.2.0" git-url-parse: "npm:^15.0.0" @@ -3260,11 +3372,9 @@ __metadata: handlebars: "npm:^4.7.3" html-webpack-plugin: "npm:^5.6.3" inquirer: "npm:^8.2.0" - jest: "npm:^29.7.0" - jest-cli: "npm:^29.7.0" + jest: "npm:^30.2.0" jest-css-modules: "npm:^2.1.0" - jest-environment-jsdom: "npm:^29.0.2" - jest-runtime: "npm:^29.0.2" + jsdom: "npm:^27.1.0" json-schema: "npm:^0.4.0" lodash: "npm:^4.17.21" mini-css-extract-plugin: "npm:^2.4.2" @@ -3298,7 +3408,7 @@ __metadata: ts-morph: "npm:^24.0.0" undici: "npm:^7.2.3" util: "npm:^0.12.3" - webpack: "npm:~5.96.0" + webpack: "npm:~5.103.0" webpack-dev-server: "npm:^5.0.0" yaml: "npm:^2.0.0" yargs: "npm:^16.2.0" @@ -3307,16 +3417,22 @@ __metadata: zod: "npm:^3.22.4" zod-validation-error: "npm:^3.4.0" peerDependencies: + "@jest/environment-jsdom-abstract": ^30.0.0 "@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 + jest: ^29.0.0 || ^30.0.0 + jest-environment-jsdom: "*" + jsdom: ^27.1.0 mini-css-extract-plugin: ^2.4.2 terser-webpack-plugin: ^5.1.3 - webpack: ~5.96.0 + webpack: ~5.103.0 webpack-dev-server: ^5.0.0 peerDependenciesMeta: + "@jest/environment-jsdom-abstract": + optional: true "@module-federation/enhanced": optional: true "@pmmmwh/react-refresh-webpack-plugin": @@ -3327,6 +3443,10 @@ __metadata: optional: true fork-ts-checker-webpack-plugin: optional: true + jest-environment-jsdom: + optional: true + jsdom: + optional: true mini-css-extract-plugin: optional: true terser-webpack-plugin: @@ -3347,7 +3467,7 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/cli-common": "workspace:^" "@types/jscodeshift": "npm:^0.12.0" - "@types/node": "npm:^20.16.0" + "@types/node": "npm:^22.13.14" chalk: "npm:^4.0.0" commander: "npm:^12.0.0" jscodeshift: "npm:^0.16.0" @@ -3378,8 +3498,8 @@ __metadata: json-schema-traverse: "npm:^1.0.0" lodash: "npm:^4.17.21" minimist: "npm:^1.2.5" - msw: "npm:^1.0.0" - typescript-json-schema: "npm:^0.65.0" + msw: "npm:^2.0.0" + typescript-json-schema: "npm:^0.67.0" yaml: "npm:^2.0.0" zen-observable: "npm:^0.10.0" languageName: unknown @@ -3598,7 +3718,7 @@ __metadata: "@types/command-exists": "npm:^1.2.0" "@types/fs-extra": "npm:^11.0.0" "@types/inquirer": "npm:^8.1.3" - "@types/node": "npm:^20.16.0" + "@types/node": "npm:^22.13.14" "@types/recursive-readdir": "npm:^2.2.0" chalk: "npm:^4.0.0" commander: "npm:^12.0.0" @@ -3793,19 +3913,17 @@ __metadata: resolution: "@backstage/frontend-plugin-api@workspace:packages/frontend-plugin-api" dependencies: "@backstage/cli": "workspace:^" - "@backstage/core-components": "workspace:^" - "@backstage/core-plugin-api": "workspace:^" + "@backstage/config": "workspace:^" + "@backstage/errors": "workspace:^" "@backstage/frontend-app-api": "workspace:^" "@backstage/frontend-test-utils": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/types": "workspace:^" "@backstage/version-bridge": "workspace:^" - "@material-ui/core": "npm:^4.12.4" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" "@types/react": "npm:^18.0.0" history: "npm:^5.3.0" - lodash: "npm:^4.17.21" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" react-router-dom: "npm:^6.3.0" @@ -3914,7 +4032,6 @@ __metadata: "@backstage/errors": "workspace:^" "@octokit/auth-app": "npm:^4.0.0" "@octokit/rest": "npm:^19.0.3" - "@types/luxon": "npm:^3.0.0" cross-fetch: "npm:^4.0.0" git-url-parse: "npm:^15.0.0" lodash: "npm:^4.17.21" @@ -3953,7 +4070,6 @@ __metadata: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/core-app-api": "workspace:^" - "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" @@ -4010,7 +4126,7 @@ __metadata: "@backstage/types": "workspace:^" "@types/express": "npm:^4.17.6" "@types/supertest": "npm:^2.0.8" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" fs-extra: "npm:^11.2.0" globby: "npm:^11.0.0" @@ -4031,7 +4147,7 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/config-loader": "workspace:^" "@types/express": "npm:^4.17.6" - express: "npm:^4.17.1" + express: "npm:^4.22.0" fs-extra: "npm:^11.2.0" languageName: unknown linkType: soft @@ -4116,7 +4232,7 @@ __metadata: "@backstage/plugin-auth-backend": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/types": "workspace:^" - express: "npm:^4.18.2" + express: "npm:^4.22.0" passport: "npm:^0.7.0" passport-atlassian-oauth2: "npm:^2.1.0" supertest: "npm:^7.0.0" @@ -4137,7 +4253,7 @@ __metadata: "@backstage/types": "workspace:^" "@types/passport-auth0": "npm:^1.0.5" "@types/passport-oauth2": "npm:^1.4.15" - express: "npm:^4.17.1" + express: "npm:^4.22.0" passport-auth0: "npm:^1.4.3" passport-oauth2: "npm:^1.6.1" supertest: "npm:^7.0.0" @@ -4156,7 +4272,7 @@ __metadata: "@backstage/plugin-auth-backend": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/types": "workspace:^" - express: "npm:^4.18.2" + express: "npm:^4.22.0" jose: "npm:^5.0.0" msw: "npm:^2.0.8" node-cache: "npm:^5.1.2" @@ -4176,7 +4292,7 @@ __metadata: "@backstage/plugin-auth-backend": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@types/passport": "npm:^1.0.16" - express: "npm:^4.19.2" + express: "npm:^4.22.0" jose: "npm:^5.0.0" passport: "npm:^0.7.0" zod: "npm:^3.22.4" @@ -4194,7 +4310,7 @@ __metadata: "@backstage/plugin-auth-backend": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/types": "workspace:^" - express: "npm:^4.18.2" + express: "npm:^4.22.0" passport: "npm:^0.7.0" passport-bitbucket-oauth2: "npm:^0.1.2" supertest: "npm:^7.0.0" @@ -4234,7 +4350,7 @@ __metadata: "@backstage/plugin-auth-backend": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/types": "workspace:^" - express: "npm:^4.18.2" + express: "npm:^4.22.0" jose: "npm:^5.0.0" msw: "npm:^2.0.0" node-mocks-http: "npm:^1.0.0" @@ -4253,7 +4369,7 @@ __metadata: "@backstage/errors": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/types": "workspace:^" - express: "npm:^4.18.2" + express: "npm:^4.22.0" google-auth-library: "npm:^9.0.0" zod: "npm:^3.22.4" languageName: unknown @@ -4288,7 +4404,7 @@ __metadata: "@backstage/plugin-auth-backend": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/types": "workspace:^" - express: "npm:^4.18.2" + express: "npm:^4.22.0" passport: "npm:^0.7.0" passport-gitlab2: "npm:^5.0.0" supertest: "npm:^7.0.0" @@ -4325,7 +4441,7 @@ __metadata: "@backstage/config": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" - express: "npm:^4.18.2" + express: "npm:^4.22.0" passport-oauth2: "npm:^1.7.0" languageName: unknown linkType: soft @@ -4343,7 +4459,7 @@ __metadata: "@backstage/plugin-auth-node": "workspace:^" "@backstage/types": "workspace:^" "@types/passport-microsoft": "npm:^1.0.0" - express: "npm:^4.18.2" + express: "npm:^4.22.0" jose: "npm:^5.0.0" msw: "npm:^1.0.0" passport-microsoft: "npm:^1.0.0" @@ -4397,7 +4513,7 @@ __metadata: "@backstage/plugin-auth-node": "workspace:^" "@backstage/types": "workspace:^" cookie-parser: "npm:^1.4.6" - express: "npm:^4.18.2" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.1" express-session: "npm:^1.17.3" jose: "npm:^5.0.0" @@ -4421,7 +4537,7 @@ __metadata: "@backstage/plugin-auth-node": "workspace:^" "@backstage/types": "workspace:^" "@davidzemon/passport-okta-oauth": "npm:^0.0.7" - express: "npm:^4.18.2" + express: "npm:^4.22.0" passport: "npm:^0.7.0" supertest: "npm:^7.0.0" zod: "npm:^3.22.4" @@ -4439,7 +4555,7 @@ __metadata: "@backstage/plugin-auth-backend": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/types": "workspace:^" - express: "npm:^4.18.2" + express: "npm:^4.22.0" passport: "npm:^0.7.0" passport-onelogin-oauth: "npm:^0.0.1" supertest: "npm:^7.0.0" @@ -4460,7 +4576,7 @@ __metadata: "@backstage/plugin-auth-backend": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/types": "workspace:^" - express: "npm:^4.18.2" + express: "npm:^4.22.0" msw: "npm:^2.7.3" passport-oauth2: "npm:^1.8.0" supertest: "npm:^7.1.0" @@ -4481,7 +4597,7 @@ __metadata: "@backstage/plugin-auth-node": "workspace:^" "@backstage/types": "workspace:^" cookie-parser: "npm:^1.4.6" - express: "npm:^4.18.2" + express: "npm:^4.22.0" express-session: "npm:^1.17.3" jose: "npm:^5.0.0" luxon: "npm:^3.4.3" @@ -4537,7 +4653,7 @@ __metadata: "@types/passport": "npm:^1.0.3" connect-session-knex: "npm:^4.0.0" cookie-parser: "npm:^1.4.5" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" express-session: "npm:^1.17.1" jose: "npm:^5.0.0" @@ -4568,7 +4684,7 @@ __metadata: "@types/express": "npm:^4.17.6" "@types/passport": "npm:^1.0.3" cookie-parser: "npm:^1.4.6" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.1" jose: "npm:^5.0.0" lodash: "npm:^4.17.21" @@ -4917,8 +5033,7 @@ __metadata: "@backstage/types": "workspace:^" "@opentelemetry/api": "npm:^1.9.0" "@types/express": "npm:^4.17.6" - "@types/luxon": "npm:^3.0.0" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" knex: "npm:^3.0.0" luxon: "npm:^3.0.0" @@ -5080,7 +5195,7 @@ __metadata: better-sqlite3: "npm:^12.0.0" codeowners-utils: "npm:^1.0.2" core-js: "npm:^3.6.5" - express: "npm:^4.17.1" + express: "npm:^4.22.0" fast-json-stable-stringify: "npm:^2.1.0" fs-extra: "npm:^11.2.0" git-url-parse: "npm:^15.0.0" @@ -5121,7 +5236,6 @@ __metadata: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/core-app-api": "workspace:^" - "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" @@ -5166,7 +5280,6 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/core-app-api": "workspace:^" - "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" @@ -5288,7 +5401,9 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-unprocessed-entities-common@workspace:plugins/catalog-unprocessed-entities-common" dependencies: + "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" + "@backstage/errors": "workspace:^" "@backstage/plugin-permission-common": "workspace:^" languageName: unknown linkType: soft @@ -5297,14 +5412,13 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-unprocessed-entities@workspace:plugins/catalog-unprocessed-entities" dependencies: - "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" - "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" + "@backstage/plugin-catalog-unprocessed-entities-common": "workspace:^" "@material-ui/core": "npm:^4.9.13" "@material-ui/icons": "npm:^4.9.1" "@material-ui/lab": "npm:^4.0.0-alpha.60" @@ -5445,7 +5559,7 @@ __metadata: "@types/yarnpkg__lockfile": "npm:^1.1.4" "@yarnpkg/lockfile": "npm:^1.1.0" "@yarnpkg/parsers": "npm:^3.0.0" - express: "npm:^4.18.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" fs-extra: "npm:^11.0.0" lodash: "npm:^4.17.21" @@ -5483,7 +5597,9 @@ __metadata: "@material-ui/icons": "npm:^4.9.1" "@material-ui/lab": "npm:^4.0.0-alpha.57" "@testing-library/jest-dom": "npm:^6.0.0" + "@types/lodash": "npm:^4.14.151" "@types/react": "npm:^18.0.0" + lodash: "npm:^4.17.21" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" react-json-view: "npm:^1.21.3" @@ -5660,7 +5776,7 @@ __metadata: "@types/content-type": "npm:^1.1.8" "@types/express": "npm:^4.17.6" content-type: "npm:^1.0.5" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" knex: "npm:^3.0.0" supertest: "npm:^7.0.0" @@ -5680,8 +5796,8 @@ __metadata: "@types/express": "npm:^4.17.6" content-type: "npm:^1.0.5" cross-fetch: "npm:^4.0.0" - express: "npm:^4.17.1" - msw: "npm:^1.0.0" + express: "npm:^4.22.0" + msw: "npm:^2.0.0" uri-template: "npm:^2.0.0" languageName: unknown linkType: soft @@ -5698,8 +5814,8 @@ __metadata: "@opentelemetry/api": "npm:^1.9.0" "@opentelemetry/core": "npm:^1.29.0" "@types/express": "npm:^4.17.6" - eventsource: "npm:^3.0.6" - express: "npm:^4.17.1" + eventsource: "npm:^4.0.0" + express: "npm:^4.22.0" http-proxy-middleware: "npm:^3.0.3" wait-for-expect: "npm:^3.0.2" languageName: unknown @@ -5715,7 +5831,7 @@ __metadata: "@backstage/frontend-plugin-api": "workspace:^" "@material-ui/core": "npm:^4.12.2" "@material-ui/icons": "npm:^4.9.1" - "@rjsf/utils": "npm:5.23.2" + "@rjsf/utils": "npm:5.24.13" "@types/react": "npm:^18.0.0" "@types/react-grid-layout": "npm:^1.3.2" react: "npm:^18.0.2" @@ -5741,7 +5857,6 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/core-app-api": "workspace:^" - "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" @@ -5753,10 +5868,10 @@ __metadata: "@material-ui/core": "npm:^4.12.2" "@material-ui/icons": "npm:^4.9.1" "@material-ui/lab": "npm:4.0.0-alpha.61" - "@rjsf/core": "npm:5.23.2" - "@rjsf/material-ui": "npm:5.23.2" - "@rjsf/utils": "npm:5.23.2" - "@rjsf/validator-ajv8": "npm:5.23.2" + "@rjsf/core": "npm:5.24.13" + "@rjsf/material-ui": "npm:5.24.13" + "@rjsf/utils": "npm:5.24.13" + "@rjsf/validator-ajv8": "npm:5.24.13" "@testing-library/dom": "npm:^10.0.0" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" @@ -5813,8 +5928,7 @@ __metadata: "@smithy/signature-v4": "npm:^4.1.0" "@types/express": "npm:^4.17.6" "@types/http-proxy-middleware": "npm:^1.0.0" - "@types/luxon": "npm:^3.0.0" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" fs-extra: "npm:^11.2.0" http-proxy-middleware: "npm:^2.0.6" @@ -5845,7 +5959,7 @@ __metadata: "@material-ui/lab": "npm:4.0.0-alpha.61" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" - "@types/node": "npm:^20.16.0" + "@types/node": "npm:^22.13.14" "@types/react": "npm:^18.0.0" kubernetes-models: "npm:^4.1.0" react: "npm:^18.0.2" @@ -5892,6 +6006,7 @@ __metadata: "@backstage/plugin-kubernetes-common": "workspace:^" "@backstage/types": "workspace:^" "@kubernetes/client-node": "npm:1.4.0" + "@types/express": "npm:^4.17.6" msw: "npm:^1.3.1" node-fetch: "npm:^2.7.0" supertest: "npm:^7.0.0" @@ -5930,7 +6045,7 @@ __metadata: kubernetes-models: "npm:^4.3.1" lodash: "npm:^4.17.21" luxon: "npm:^3.0.0" - msw: "npm:^1.3.1" + msw: "npm:^2.0.0" react: "npm:^18.0.2" react-dom: "npm:^18.0.2" react-router-dom: "npm:^6.3.0" @@ -5952,7 +6067,6 @@ __metadata: dependencies: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" - "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" @@ -5993,9 +6107,10 @@ __metadata: "@backstage/errors": "workspace:^" "@backstage/plugin-catalog-node": "workspace:^" "@backstage/types": "workspace:^" - "@modelcontextprotocol/sdk": "npm:^1.12.3" + "@cfworker/json-schema": "npm:^4.1.1" + "@modelcontextprotocol/sdk": "npm:^1.24.3" "@types/express": "npm:^4.17.6" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" zod: "npm:^3.22.4" languageName: unknown @@ -6006,7 +6121,6 @@ __metadata: resolution: "@backstage/plugin-mui-to-bui@workspace:plugins/mui-to-bui" dependencies: "@backstage/cli": "workspace:^" - "@backstage/core-compat-api": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" @@ -6050,7 +6164,7 @@ __metadata: "@backstage/plugin-notifications-common": "workspace:^" "@backstage/plugin-notifications-node": "workspace:^" "@backstage/types": "workspace:^" - "@types/nodemailer": "npm:^6.4.14" + "@types/nodemailer": "npm:^7.0.0" lodash: "npm:^4.17.21" nodemailer: "npm:^7.0.7" p-throttle: "npm:^4.1.1" @@ -6072,7 +6186,7 @@ __metadata: "@backstage/plugin-notifications-node": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/types": "workspace:^" - "@faker-js/faker": "npm:^8.4.1" + "@faker-js/faker": "npm:^10.0.0" "@opentelemetry/api": "npm:^1.9.0" "@slack/bolt": "npm:^3.21.4" "@slack/types": "npm:^2.14.0" @@ -6105,7 +6219,7 @@ __metadata: "@backstage/types": "workspace:^" "@types/express": "npm:^4.17.6" "@types/supertest": "npm:^2.0.8" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" knex: "npm:^3.0.0" p-throttle: "npm:^4.1.1" @@ -6148,7 +6262,6 @@ __metadata: resolution: "@backstage/plugin-notifications@workspace:plugins/notifications" dependencies: "@backstage/cli": "workspace:^" - "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" @@ -6227,7 +6340,6 @@ __metadata: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/core-app-api": "workspace:^" - "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" @@ -6296,7 +6408,7 @@ __metadata: "@types/lodash": "npm:^4.14.151" "@types/supertest": "npm:^2.0.8" dataloader: "npm:^2.0.0" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" lodash: "npm:^4.17.21" msw: "npm:^1.0.0" @@ -6336,7 +6448,7 @@ __metadata: "@backstage/plugin-permission-common": "workspace:^" "@types/express": "npm:^4.17.6" "@types/supertest": "npm:^2.0.8" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" msw: "npm:^1.0.0" supertest: "npm:^7.0.0" @@ -6387,7 +6499,7 @@ __metadata: "@backstage/types": "workspace:^" "@types/express": "npm:^4.17.6" "@types/http-proxy-middleware": "npm:^1.0.0" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" http-proxy-middleware: "npm:^2.0.0" msw: "npm:^2.0.0" @@ -6652,7 +6764,7 @@ __metadata: "@backstage/types": "workspace:^" "@types/command-exists": "npm:^1.2.0" "@types/fs-extra": "npm:^11.0.0" - "@types/node": "npm:^20.16.0" + "@types/node": "npm:^22.13.14" command-exists: "npm:^1.2.9" fs-extra: "npm:^11.0.0" jest-when: "npm:^3.1.0" @@ -6734,12 +6846,12 @@ __metadata: "@types/supertest": "npm:^2.0.8" "@types/zen-observable": "npm:^0.8.0" concat-stream: "npm:^2.0.0" - esbuild: "npm:^0.25.0" - express: "npm:^4.17.1" + esbuild: "npm:^0.27.0" + express: "npm:^4.22.0" fs-extra: "npm:^11.2.0" globby: "npm:^11.0.0" isbinaryfile: "npm:^5.0.0" - isolated-vm: "npm:^5.0.1" + isolated-vm: "npm:^6.0.1" jsonschema: "npm:^1.5.0" knex: "npm:^3.0.0" lodash: "npm:^4.17.21" @@ -6867,17 +6979,16 @@ __metadata: "@material-ui/icons": "npm:^4.9.1" "@material-ui/lab": "npm:4.0.0-alpha.61" "@react-hookz/web": "npm:^24.0.0" - "@rjsf/core": "npm:5.23.2" - "@rjsf/material-ui": "npm:5.23.2" - "@rjsf/utils": "npm:5.23.2" - "@rjsf/validator-ajv8": "npm:5.23.2" + "@rjsf/core": "npm:5.24.13" + "@rjsf/material-ui": "npm:5.24.13" + "@rjsf/utils": "npm:5.24.13" + "@rjsf/validator-ajv8": "npm:5.24.13" "@testing-library/dom": "npm:^10.0.0" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" "@testing-library/user-event": "npm:^14.0.0" "@types/humanize-duration": "npm:^3.18.1" "@types/json-schema": "npm:^7.0.9" - "@types/luxon": "npm:^3.0.0" "@types/react": "npm:^18.0.0" ajv: "npm:^8.0.1" ajv-errors: "npm:^3.0.0" @@ -6895,7 +7006,7 @@ __metadata: react-router-dom: "npm:^6.3.0" react-use: "npm:^17.2.4" swr: "npm:^2.0.0" - use-immer: "npm:^0.10.0" + use-immer: "npm:^0.11.0" zen-observable: "npm:^0.10.0" zod: "npm:^3.22.4" zod-to-json-schema: "npm:^3.20.4" @@ -6918,7 +7029,6 @@ __metadata: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/core-app-api": "workspace:^" - "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" @@ -6945,10 +7055,10 @@ __metadata: "@material-ui/icons": "npm:^4.9.1" "@material-ui/lab": "npm:4.0.0-alpha.61" "@react-hookz/web": "npm:^24.0.0" - "@rjsf/core": "npm:5.23.2" - "@rjsf/material-ui": "npm:5.23.2" - "@rjsf/utils": "npm:5.23.2" - "@rjsf/validator-ajv8": "npm:5.23.2" + "@rjsf/core": "npm:5.24.13" + "@rjsf/material-ui": "npm:5.24.13" + "@rjsf/utils": "npm:5.24.13" + "@rjsf/validator-ajv8": "npm:5.24.13" "@testing-library/dom": "npm:^10.0.0" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" @@ -7037,7 +7147,7 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-search-backend-module-explore@workspace:plugins/search-backend-module-explore" dependencies: - "@backstage-community/plugin-explore-common": "npm:^0.5.0" + "@backstage-community/plugin-explore-common": "npm:^0.9.0" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" @@ -7142,7 +7252,7 @@ __metadata: "@types/express": "npm:^4.17.6" "@types/supertest": "npm:^2.0.8" dataloader: "npm:^2.0.0" - express: "npm:^4.17.1" + express: "npm:^4.22.0" lodash: "npm:^4.17.21" qs: "npm:^6.10.1" supertest: "npm:^7.0.0" @@ -7209,7 +7319,6 @@ __metadata: dependencies: "@backstage/cli": "workspace:^" "@backstage/core-app-api": "workspace:^" - "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" @@ -7263,7 +7372,7 @@ __metadata: "@types/express": "npm:^4.17.6" "@types/supertest": "npm:^2.0.8" "@types/ws": "npm:^8.5.10" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" supertest: "npm:^7.0.0" uuid: "npm:^11.0.0" @@ -7282,7 +7391,7 @@ __metadata: "@backstage/plugin-events-node": "workspace:^" "@backstage/types": "workspace:^" "@types/express": "npm:^4.17.21" - express: "npm:^4.17.1" + express: "npm:^4.22.0" uuid: "npm:^11.0.0" ws: "npm:^8.18.0" languageName: unknown @@ -7319,7 +7428,6 @@ __metadata: resolution: "@backstage/plugin-signals@workspace:plugins/signals" dependencies: "@backstage/cli": "workspace:^" - "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" @@ -7369,7 +7477,7 @@ __metadata: react: "npm:^18.0.2" react-dom: "npm:^18.0.2" react-router-dom: "npm:^6.3.0" - testing-library__dom: "npm:^7.29.4-beta.1" + shadow-dom-testing-library: "npm:^1.13.1" peerDependencies: "@testing-library/react": ^16.0.0 "@types/react": ^17.0.0 || ^18.0.0 @@ -7399,7 +7507,7 @@ __metadata: "@backstage/plugin-techdocs-node": "workspace:^" "@backstage/types": "workspace:^" "@types/express": "npm:^4.17.6" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" fs-extra: "npm:^11.2.0" knex: "npm:^3.0.0" @@ -7444,6 +7552,7 @@ __metadata: react: "npm:^18.0.2" react-dom: "npm:^18.0.2" react-router-dom: "npm:^6.3.0" + shadow-dom-testing-library: "npm:^1.13.1" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 @@ -7486,7 +7595,7 @@ __metadata: "@types/supertest": "npm:^2.0.8" aws-sdk-client-mock: "npm:^4.0.0" dockerode: "npm:^4.0.0" - express: "npm:^4.17.1" + express: "npm:^4.22.0" fs-extra: "npm:^11.2.0" git-url-parse: "npm:^15.0.0" hpagent: "npm:^1.2.0" @@ -7546,7 +7655,6 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/core-app-api": "workspace:^" - "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" @@ -7609,7 +7717,7 @@ __metadata: "@backstage/types": "workspace:^" "@types/express": "npm:^4.17.6" "@types/supertest": "npm:^2.0.8" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" knex: "npm:^3.0.0" supertest: "npm:^7.0.0" @@ -7631,7 +7739,6 @@ __metadata: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/core-app-api": "workspace:^" - "@backstage/core-compat-api": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" @@ -7696,8 +7803,8 @@ __metadata: "@backstage/types": "workspace:^" "@electric-sql/pglite": "npm:^0.3.0" "@manypkg/get-packages": "npm:^1.1.3" - "@microsoft/api-documenter": "npm:^7.25.7" - "@microsoft/api-extractor": "npm:^7.47.2" + "@microsoft/api-documenter": "npm:^7.28.1" + "@microsoft/api-extractor": "npm:^7.55.1" "@openapitools/openapi-generator-cli": "npm:^2.7.0" "@stoplight/spectral-core": "npm:^1.18.0" "@stoplight/spectral-formatters": "npm:^1.1.0" @@ -7707,7 +7814,7 @@ __metadata: "@stoplight/spectral-runtime": "npm:^1.1.2" "@stoplight/types": "npm:^14.0.0" "@types/is-glob": "npm:^4.0.2" - "@types/node": "npm:^20.16.0" + "@types/node": "npm:^22.13.14" "@types/prettier": "npm:^2.0.0" "@useoptic/openapi-utilities": "npm:^0.55.0" chalk: "npm:^4.0.0" @@ -7836,7 +7943,7 @@ __metadata: "@types/react-dom": "npm:^18.0.0" chalk: "npm:^5.4.1" clsx: "npm:^2.1.1" - eslint-plugin-storybook: "npm:^9.1.7" + eslint-plugin-storybook: "npm:^10.1.9" glob: "npm:^11.0.1" globals: "npm:^15.11.0" lightningcss: "npm:^1.29.1" @@ -7845,7 +7952,7 @@ __metadata: react-aria-components: "npm:^1.13.0" react-dom: "npm:^18.0.2" react-router-dom: "npm:^6.3.0" - storybook: "npm:^9.1.7" + storybook: "npm:^10.1.10" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 @@ -7911,11 +8018,18 @@ __metadata: languageName: node linkType: hard -"@changesets/apply-release-plan@npm:^7.0.13": - version: 7.0.13 - resolution: "@changesets/apply-release-plan@npm:7.0.13" +"@cfworker/json-schema@npm:^4.1.1": + version: 4.1.1 + resolution: "@cfworker/json-schema@npm:4.1.1" + checksum: 10/62fd08bb2e6b4f0fe7c2b8f8c19f17f94b6a34feba7f455f228898ab435eda8aae082fcf6b0fe8a235a72e0ec0041922fdcd4c526acc32d45084272f000c1af9 + languageName: node + linkType: hard + +"@changesets/apply-release-plan@npm:^7.0.14": + version: 7.0.14 + resolution: "@changesets/apply-release-plan@npm:7.0.14" dependencies: - "@changesets/config": "npm:^3.1.1" + "@changesets/config": "npm:^3.1.2" "@changesets/get-version-range-type": "npm:^0.4.0" "@changesets/git": "npm:^3.0.4" "@changesets/should-skip-package": "npm:^0.1.2" @@ -7928,7 +8042,7 @@ __metadata: prettier: "npm:^2.7.1" resolve-from: "npm:^5.0.0" semver: "npm:^7.5.3" - checksum: 10/b2ef4fc9a68ffd5c0543f0a98b8ea2321ff58519d541720646692a03844a2cd8e860ebcb93846be1e062926414dc343333196bfd8806fab26f637e8db8adbb9e + checksum: 10/7735783734bddd6d628e3a18c6de253685504c18f580636979fe558dda88501c8e4bda28c34a2f9da96f80fae0d1228271857d86fff6045226ce04b18d8b98b6 languageName: node linkType: hard @@ -7956,24 +8070,24 @@ __metadata: linkType: hard "@changesets/cli@npm:^2.14.0": - version: 2.29.7 - resolution: "@changesets/cli@npm:2.29.7" + version: 2.29.8 + resolution: "@changesets/cli@npm:2.29.8" dependencies: - "@changesets/apply-release-plan": "npm:^7.0.13" + "@changesets/apply-release-plan": "npm:^7.0.14" "@changesets/assemble-release-plan": "npm:^6.0.9" "@changesets/changelog-git": "npm:^0.2.1" - "@changesets/config": "npm:^3.1.1" + "@changesets/config": "npm:^3.1.2" "@changesets/errors": "npm:^0.2.0" "@changesets/get-dependents-graph": "npm:^2.1.3" - "@changesets/get-release-plan": "npm:^4.0.13" + "@changesets/get-release-plan": "npm:^4.0.14" "@changesets/git": "npm:^3.0.4" "@changesets/logger": "npm:^0.1.1" "@changesets/pre": "npm:^2.0.2" - "@changesets/read": "npm:^0.6.5" + "@changesets/read": "npm:^0.6.6" "@changesets/should-skip-package": "npm:^0.1.2" "@changesets/types": "npm:^6.1.0" "@changesets/write": "npm:^0.4.0" - "@inquirer/external-editor": "npm:^1.0.0" + "@inquirer/external-editor": "npm:^1.0.2" "@manypkg/get-packages": "npm:^1.1.3" ansi-colors: "npm:^4.1.3" ci-info: "npm:^3.7.0" @@ -7989,13 +8103,13 @@ __metadata: term-size: "npm:^2.1.0" bin: changeset: bin.js - checksum: 10/e44ee8e9a09ffc990707ec272b03f5724890e6d8833815b80265a9e62f2784ee3fa76c858469fa95c53e1dabd9a0500a6c36b1343211fc0a38902d8fd1b1fce5 + checksum: 10/1169d97d7d0b86fdeb778aadc1ffa3e46c840345c97b4cdbe90e5fc0168d0d0870001d66f6676537716a2d22c147a84e8a120f1298156419dc6a662681861af5 languageName: node linkType: hard -"@changesets/config@npm:^3.1.1": - version: 3.1.1 - resolution: "@changesets/config@npm:3.1.1" +"@changesets/config@npm:^3.1.2": + version: 3.1.2 + resolution: "@changesets/config@npm:3.1.2" dependencies: "@changesets/errors": "npm:^0.2.0" "@changesets/get-dependents-graph": "npm:^2.1.3" @@ -8004,7 +8118,7 @@ __metadata: "@manypkg/get-packages": "npm:^1.1.3" fs-extra: "npm:^7.0.1" micromatch: "npm:^4.0.8" - checksum: 10/9500e02b68801f052478b3e10523bd3a39b9e5e989e718832832537c9da965580f496262c2bc3f6e23a4e6fb4303f730a69dcbf2041f68d2fa7bd03dd1f82db0 + checksum: 10/c35626240c0af83433808216be48cc39dd0b27d20a7d3bbb95c0da0044a08207986678dae97f081cc524abf8351e0303890794a28e8c67f17036bd88013b2576 languageName: node linkType: hard @@ -8029,17 +8143,17 @@ __metadata: languageName: node linkType: hard -"@changesets/get-release-plan@npm:^4.0.13": - version: 4.0.13 - resolution: "@changesets/get-release-plan@npm:4.0.13" +"@changesets/get-release-plan@npm:^4.0.14": + version: 4.0.14 + resolution: "@changesets/get-release-plan@npm:4.0.14" dependencies: "@changesets/assemble-release-plan": "npm:^6.0.9" - "@changesets/config": "npm:^3.1.1" + "@changesets/config": "npm:^3.1.2" "@changesets/pre": "npm:^2.0.2" - "@changesets/read": "npm:^0.6.5" + "@changesets/read": "npm:^0.6.6" "@changesets/types": "npm:^6.1.0" "@manypkg/get-packages": "npm:^1.1.3" - checksum: 10/9983fae5a68012c4c418ddd62f2fb3d325363f21160252ff7b868503a1a2effb8fdd32e4a0289b72653afc3605ce19d163ff69205c942a0004efb571a5f78fd0 + checksum: 10/0b54f4e34dc27aa9df928488bf84f3d6a2b516701985d06b49306d45b87b48e642aef3de751f9517de4c1b88011b5826975aa85f8ba596da1f9681a5d1699093 languageName: node linkType: hard @@ -8072,13 +8186,13 @@ __metadata: languageName: node linkType: hard -"@changesets/parse@npm:^0.4.1": - version: 0.4.1 - resolution: "@changesets/parse@npm:0.4.1" +"@changesets/parse@npm:^0.4.2": + version: 0.4.2 + resolution: "@changesets/parse@npm:0.4.2" dependencies: "@changesets/types": "npm:^6.1.0" - js-yaml: "npm:^3.13.1" - checksum: 10/2973ab8f38592a80efea589e148e5bdfd6ed3af86aa9206f941b5b3955f68464bf70a5965349f642667c708ebae60e4266be538328cd27075cace3f7cc1022e3 + js-yaml: "npm:^4.1.1" + checksum: 10/d45d7f5d7a0aeede197935f16bb459479c8d0b16ebe89ceaf4bd58b307ef1be696bcc5d5fc33d5b64a80dec946b49f6107af32d57d91967e6b3f9013a0d53740 languageName: node linkType: hard @@ -8094,18 +8208,18 @@ __metadata: languageName: node linkType: hard -"@changesets/read@npm:^0.6.5": - version: 0.6.5 - resolution: "@changesets/read@npm:0.6.5" +"@changesets/read@npm:^0.6.6": + version: 0.6.6 + resolution: "@changesets/read@npm:0.6.6" dependencies: "@changesets/git": "npm:^3.0.4" "@changesets/logger": "npm:^0.1.1" - "@changesets/parse": "npm:^0.4.1" + "@changesets/parse": "npm:^0.4.2" "@changesets/types": "npm:^6.1.0" fs-extra: "npm:^7.0.1" p-filter: "npm:^2.1.0" picocolors: "npm:^1.1.0" - checksum: 10/fec0ac28801e0560fae0eb1d21250dd2a48aaff67bddd1b446a960afd761690d5873dca6eff369d43763bec61f1023d38a38876d5824e316e6de622dc52a24f3 + checksum: 10/3ac0cf24159b0e0fea4339d0a01c57459a6b7796f868dca7db65727c3dd33ead38b78f224b677cf7b50bb7b96fa3d0b155843e800a524b435a772c9ed21fa914 languageName: node linkType: hard @@ -8241,14 +8355,14 @@ __metadata: linkType: hard "@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.23.0": - version: 6.38.6 - resolution: "@codemirror/view@npm:6.38.6" + 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/5a047337a98de111817ce8c8d39e6429c90ca0b0a4d2678d6e161e9e5961b1d476a891f447ab7a05cac395d4a93530e7c68bedd93191285265f0742a308ad00b + checksum: 10/81b1508015a378e4719d0239254173f0c5cd340c2abf96eb488fe5fb474bdb37ec1f010b9890ced774accd7aeb9443e7337cb6a89544b954273e5ddabece7cea languageName: node linkType: hard @@ -8268,14 +8382,69 @@ __metadata: languageName: node linkType: hard -"@dabh/diagnostics@npm:^2.0.2": - version: 2.0.2 - resolution: "@dabh/diagnostics@npm:2.0.2" +"@csstools/color-helpers@npm:^5.1.0": + version: 5.1.0 + resolution: "@csstools/color-helpers@npm:5.1.0" + checksum: 10/0138b3d5ccbe77aeccf6721fd008a53523c70e932f0c82dca24a1277ca780447e1d8357da47512ebf96358476f8764de57002f3e491920d67e69202f5a74c383 + languageName: node + linkType: hard + +"@csstools/css-calc@npm:^2.1.3, @csstools/css-calc@npm:^2.1.4": + version: 2.1.4 + resolution: "@csstools/css-calc@npm:2.1.4" + peerDependencies: + "@csstools/css-parser-algorithms": ^3.0.5 + "@csstools/css-tokenizer": ^3.0.4 + checksum: 10/06975b650c0f44c60eeb7afdb3fd236f2dd607b2c622e0bc908d3f54de39eb84e0692833320d03dac04bd6c1ab0154aa3fa0dd442bd9e5f917cf14d8e2ba8d74 + languageName: node + linkType: hard + +"@csstools/css-color-parser@npm:^3.0.9, @csstools/css-color-parser@npm:^3.1.0": + version: 3.1.0 + resolution: "@csstools/css-color-parser@npm:3.1.0" dependencies: - colorspace: "npm:1.1.x" + "@csstools/color-helpers": "npm:^5.1.0" + "@csstools/css-calc": "npm:^2.1.4" + peerDependencies: + "@csstools/css-parser-algorithms": ^3.0.5 + "@csstools/css-tokenizer": ^3.0.4 + checksum: 10/4741095fdc4501e8e7ada4ed14fbf9dbbe6fea9b989818790ebca15657c29c62defbebacf18592cde2aa638a1d098bbe86d742d2c84ba932fbc00fac51cb8805 + languageName: node + linkType: hard + +"@csstools/css-parser-algorithms@npm:^3.0.4, @csstools/css-parser-algorithms@npm:^3.0.5": + version: 3.0.5 + resolution: "@csstools/css-parser-algorithms@npm:3.0.5" + peerDependencies: + "@csstools/css-tokenizer": ^3.0.4 + checksum: 10/e93083b5cb36a3c1e7a47ce10cf62961d05bd1e4c608bb3ee50186ff740157ab0ec16a3956f7b86251efd10703034d849693201eea858ae904848c68d2d46ada + languageName: node + linkType: hard + +"@csstools/css-syntax-patches-for-csstree@npm:1.0.14": + version: 1.0.14 + resolution: "@csstools/css-syntax-patches-for-csstree@npm:1.0.14" + peerDependencies: + postcss: ^8.4 + checksum: 10/c783d5db307552f483d95266452a7765ca138a9e64f12d013c63e960c9c8abbf82c899a34028af1f5ad714e0e94edd97b1aa31784923c1d7d1756d775c3c1d0a + languageName: node + linkType: hard + +"@csstools/css-tokenizer@npm:^3.0.3, @csstools/css-tokenizer@npm:^3.0.4": + version: 3.0.4 + resolution: "@csstools/css-tokenizer@npm:3.0.4" + checksum: 10/eb6c84c086312f6bb8758dfe2c85addd7475b0927333c5e39a4d59fb210b9810f8c346972046f95e60a721329cffe98895abe451e51de753ad1ca7a8c24ec65f + languageName: node + linkType: hard + +"@dabh/diagnostics@npm:^2.0.8": + version: 2.0.8 + resolution: "@dabh/diagnostics@npm:2.0.8" + dependencies: + "@so-ric/colorspace": "npm:^1.1.6" enabled: "npm:2.0.x" kuler: "npm:^2.0.0" - checksum: 10/d0c7ae32da9fc6061272ef56cf2c5af9c255e034783b68cfe3b0d47e806145d0723e6f5743e4c9fc0abae73658ca309498572688da2fbcfc56c16a8671dbe707 + checksum: 10/ac2267a4ee1874f608493f21d386ea29f0acac6716124e26e3e48e01ce5706b095585a14adce1bee14b6567d3b8fdd0c5a0bbb7ab0e15c9a743d55eb02f093ce languageName: node linkType: hard @@ -8347,14 +8516,14 @@ __metadata: linkType: hard "@elastic/elasticsearch@npm:^7.13.0": - version: 7.17.0 - resolution: "@elastic/elasticsearch@npm:7.17.0" + version: 7.17.14 + resolution: "@elastic/elasticsearch@npm:7.17.14" dependencies: debug: "npm:^4.3.1" hpagent: "npm:^0.1.1" ms: "npm:^2.1.3" secure-json-parse: "npm:^2.4.0" - checksum: 10/d54330ce50b4951b7b9db15349413b4961040fb0b73a09d3f07cef5cb2873fd22af17307e07b6c8b1b1e0844e76e9aeb78ce1e01d67a940e3190763a875648be + checksum: 10/ef26489eb8db667f0a6c2be4641e9b388bc5badd2ab7791eb82dffa138653642678f7e6ab2741b914747e95e0d43bf9daa0821bc509318c14cb40869cc9ebbc9 languageName: node linkType: hard @@ -8372,22 +8541,22 @@ __metadata: languageName: node linkType: hard -"@emnapi/core@npm:^1.5.0": - version: 1.7.0 - resolution: "@emnapi/core@npm:1.7.0" +"@emnapi/core@npm:^1.4.3, @emnapi/core@npm:^1.5.0, @emnapi/core@npm:^1.7.1": + version: 1.7.1 + resolution: "@emnapi/core@npm:1.7.1" dependencies: "@emnapi/wasi-threads": "npm:1.1.0" tslib: "npm:^2.4.0" - checksum: 10/43bb6fd7419b9589fe8190a09fce84e52c04cef171bcb1c411c278fb5380c4a76081776e5094f42c220deaf49e2e41905de5569209790ffee9b8efa59c987829 + checksum: 10/260841f6dd2a7823a964d9de6da3a5e6f565dac8d21a5bd8f6215b87c45c22a4dc371b9ad877961579ee3cca8a76e55e3dd033ae29cba1998999cda6d794bdab languageName: node linkType: hard -"@emnapi/runtime@npm:^1.5.0": - version: 1.7.0 - resolution: "@emnapi/runtime@npm:1.7.0" +"@emnapi/runtime@npm:^1.4.3, @emnapi/runtime@npm:^1.5.0, @emnapi/runtime@npm:^1.7.1": + version: 1.7.1 + resolution: "@emnapi/runtime@npm:1.7.1" dependencies: tslib: "npm:^2.4.0" - checksum: 10/4dc726eb42fe2c7777fd32090f3e5e006c630e1a732538139caa18daf586e883e81c562cd69b0622db16e76bb572a2dde30711494edcee4a34059b62f5f46267 + checksum: 10/6fc83f938e3c70e32e84c1fbe5cab6cb9340b8107cee4048384ad5b8f2998a06502b4bed342acaf6e44f473f2c14c4ab1e3fd5083bd7823fc63abfca9eff0175 languageName: node linkType: hard @@ -8419,16 +8588,16 @@ __metadata: languageName: node linkType: hard -"@emotion/cache@npm:^11.13.5": - version: 11.13.5 - resolution: "@emotion/cache@npm:11.13.5" +"@emotion/cache@npm:^11.13.5, @emotion/cache@npm:^11.14.0": + version: 11.14.0 + resolution: "@emotion/cache@npm:11.14.0" dependencies: "@emotion/memoize": "npm:^0.9.0" "@emotion/sheet": "npm:^1.4.0" "@emotion/utils": "npm:^1.4.2" "@emotion/weak-memoize": "npm:^0.4.0" stylis: "npm:4.2.0" - checksum: 10/d91139453d279cfd6f6f38180d3af2fdcee8c0fc6d9a6faa2cdce9a1211b294a8019ef45365bf1171e0687d1744a70ff760637b88ed46f7a9fe74db9dc36f4df + checksum: 10/52336b28a27b07dde8fcdfd80851cbd1487672bbd4db1e24cca1440c95d8a6a968c57b0453c2b7c88d9b432b717f99554dbecc05b5cdef27933299827e69fd8e languageName: node linkType: hard @@ -8479,14 +8648,14 @@ __metadata: linkType: hard "@emotion/react@npm:^11.10.5": - version: 11.13.5 - resolution: "@emotion/react@npm:11.13.5" + version: 11.14.0 + resolution: "@emotion/react@npm:11.14.0" dependencies: "@babel/runtime": "npm:^7.18.3" "@emotion/babel-plugin": "npm:^11.13.5" - "@emotion/cache": "npm:^11.13.5" + "@emotion/cache": "npm:^11.14.0" "@emotion/serialize": "npm:^1.3.3" - "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.1.0" + "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.2.0" "@emotion/utils": "npm:^1.4.2" "@emotion/weak-memoize": "npm:^0.4.0" hoist-non-react-statics: "npm:^3.3.1" @@ -8495,7 +8664,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10/c85bde9c6144be6f243e44d6834b16df8151b91c5136ea28d9d917155d64d1bbfba000ae98866fd9f5089546f0b0655c20b9e4280cdc5ccf39acc4a2fde37a39 + checksum: 10/3356c1d66f37f4e7abf88a2be843f6023b794b286c9c99a0aaf1cd1b2b7c50f8d80a2ef77183da737de70150f638e698ff4a2a38ab2d922f868615f1d5761c37 languageName: node linkType: hard @@ -8520,14 +8689,14 @@ __metadata: linkType: hard "@emotion/styled@npm:^11.10.5": - version: 11.13.5 - resolution: "@emotion/styled@npm:11.13.5" + version: 11.14.1 + resolution: "@emotion/styled@npm:11.14.1" dependencies: "@babel/runtime": "npm:^7.18.3" "@emotion/babel-plugin": "npm:^11.13.5" "@emotion/is-prop-valid": "npm:^1.3.0" "@emotion/serialize": "npm:^1.3.3" - "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.1.0" + "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.2.0" "@emotion/utils": "npm:^1.4.2" peerDependencies: "@emotion/react": ^11.0.0-rc.0 @@ -8535,7 +8704,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10/cf6c47f9e9292b817aaec4e34cedc996727318018b621525576a1f541cfb3ecf2443729bfba503d4467d8183ca7732fe817bd7202386f4206a9d218e6e2202d8 + checksum: 10/b20ffaaac76e16538051da8d417f1da75f47f0974000edf0999f39f309b23ee0a91ba7dc1d5f60c4017d29fadfed48631ae4a8f697e3662a88318c667d072117 languageName: node linkType: hard @@ -8560,12 +8729,12 @@ __metadata: languageName: node linkType: hard -"@emotion/use-insertion-effect-with-fallbacks@npm:^1.1.0": - version: 1.1.0 - resolution: "@emotion/use-insertion-effect-with-fallbacks@npm:1.1.0" +"@emotion/use-insertion-effect-with-fallbacks@npm:^1.2.0": + version: 1.2.0 + resolution: "@emotion/use-insertion-effect-with-fallbacks@npm:1.2.0" peerDependencies: react: ">=16.8.0" - checksum: 10/33a10f44a873b3f5ccd2a1a3d13c2f34ed628f5a2be1ccf28540a86535a14d3a930afcbef209d48346a22ec60ff48f43c86ee9c846b9480d23a55a17145da66c + checksum: 10/2374999db8d53ef661d61ed1026c42a849632e4f03826f7eba0314c1d92ae342161d737f5045453aa46dd4008e13ccefeba68d3165b667dfad8e5784fcb0c643 languageName: node linkType: hard @@ -8583,13 +8752,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/aix-ppc64@npm:0.21.5" - conditions: os=aix & cpu=ppc64 - languageName: node - linkType: hard - "@esbuild/aix-ppc64@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/aix-ppc64@npm:0.25.8" @@ -8597,10 +8759,10 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/android-arm64@npm:0.21.5" - conditions: os=android & cpu=arm64 +"@esbuild/aix-ppc64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/aix-ppc64@npm:0.27.1" + conditions: os=aix & cpu=ppc64 languageName: node linkType: hard @@ -8611,10 +8773,10 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/android-arm@npm:0.21.5" - conditions: os=android & cpu=arm +"@esbuild/android-arm64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/android-arm64@npm:0.27.1" + conditions: os=android & cpu=arm64 languageName: node linkType: hard @@ -8625,10 +8787,10 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/android-x64@npm:0.21.5" - conditions: os=android & cpu=x64 +"@esbuild/android-arm@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/android-arm@npm:0.27.1" + conditions: os=android & cpu=arm languageName: node linkType: hard @@ -8639,10 +8801,10 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/darwin-arm64@npm:0.21.5" - conditions: os=darwin & cpu=arm64 +"@esbuild/android-x64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/android-x64@npm:0.27.1" + conditions: os=android & cpu=x64 languageName: node linkType: hard @@ -8653,10 +8815,10 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/darwin-x64@npm:0.21.5" - conditions: os=darwin & cpu=x64 +"@esbuild/darwin-arm64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/darwin-arm64@npm:0.27.1" + conditions: os=darwin & cpu=arm64 languageName: node linkType: hard @@ -8667,10 +8829,10 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/freebsd-arm64@npm:0.21.5" - conditions: os=freebsd & cpu=arm64 +"@esbuild/darwin-x64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/darwin-x64@npm:0.27.1" + conditions: os=darwin & cpu=x64 languageName: node linkType: hard @@ -8681,10 +8843,10 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/freebsd-x64@npm:0.21.5" - conditions: os=freebsd & cpu=x64 +"@esbuild/freebsd-arm64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/freebsd-arm64@npm:0.27.1" + conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard @@ -8695,10 +8857,10 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-arm64@npm:0.21.5" - conditions: os=linux & cpu=arm64 +"@esbuild/freebsd-x64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/freebsd-x64@npm:0.27.1" + conditions: os=freebsd & cpu=x64 languageName: node linkType: hard @@ -8709,10 +8871,10 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-arm@npm:0.21.5" - conditions: os=linux & cpu=arm +"@esbuild/linux-arm64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/linux-arm64@npm:0.27.1" + conditions: os=linux & cpu=arm64 languageName: node linkType: hard @@ -8723,10 +8885,10 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-ia32@npm:0.21.5" - conditions: os=linux & cpu=ia32 +"@esbuild/linux-arm@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/linux-arm@npm:0.27.1" + conditions: os=linux & cpu=arm languageName: node linkType: hard @@ -8737,10 +8899,10 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-loong64@npm:0.21.5" - conditions: os=linux & cpu=loong64 +"@esbuild/linux-ia32@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/linux-ia32@npm:0.27.1" + conditions: os=linux & cpu=ia32 languageName: node linkType: hard @@ -8751,10 +8913,10 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-mips64el@npm:0.21.5" - conditions: os=linux & cpu=mips64el +"@esbuild/linux-loong64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/linux-loong64@npm:0.27.1" + conditions: os=linux & cpu=loong64 languageName: node linkType: hard @@ -8765,10 +8927,10 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-ppc64@npm:0.21.5" - conditions: os=linux & cpu=ppc64 +"@esbuild/linux-mips64el@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/linux-mips64el@npm:0.27.1" + conditions: os=linux & cpu=mips64el languageName: node linkType: hard @@ -8779,10 +8941,10 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-riscv64@npm:0.21.5" - conditions: os=linux & cpu=riscv64 +"@esbuild/linux-ppc64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/linux-ppc64@npm:0.27.1" + conditions: os=linux & cpu=ppc64 languageName: node linkType: hard @@ -8793,10 +8955,10 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-s390x@npm:0.21.5" - conditions: os=linux & cpu=s390x +"@esbuild/linux-riscv64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/linux-riscv64@npm:0.27.1" + conditions: os=linux & cpu=riscv64 languageName: node linkType: hard @@ -8807,10 +8969,10 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-x64@npm:0.21.5" - conditions: os=linux & cpu=x64 +"@esbuild/linux-s390x@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/linux-s390x@npm:0.27.1" + conditions: os=linux & cpu=s390x languageName: node linkType: hard @@ -8821,6 +8983,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-x64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/linux-x64@npm:0.27.1" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + "@esbuild/netbsd-arm64@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/netbsd-arm64@npm:0.25.8" @@ -8828,10 +8997,10 @@ __metadata: languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/netbsd-x64@npm:0.21.5" - conditions: os=netbsd & cpu=x64 +"@esbuild/netbsd-arm64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/netbsd-arm64@npm:0.27.1" + conditions: os=netbsd & cpu=arm64 languageName: node linkType: hard @@ -8842,6 +9011,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-x64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/netbsd-x64@npm:0.27.1" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/openbsd-arm64@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/openbsd-arm64@npm:0.25.8" @@ -8849,10 +9025,10 @@ __metadata: languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/openbsd-x64@npm:0.21.5" - conditions: os=openbsd & cpu=x64 +"@esbuild/openbsd-arm64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/openbsd-arm64@npm:0.27.1" + conditions: os=openbsd & cpu=arm64 languageName: node linkType: hard @@ -8863,6 +9039,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-x64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/openbsd-x64@npm:0.27.1" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/openharmony-arm64@npm:0.25.8": version: 0.25.8 resolution: "@esbuild/openharmony-arm64@npm:0.25.8" @@ -8870,10 +9053,10 @@ __metadata: languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/sunos-x64@npm:0.21.5" - conditions: os=sunos & cpu=x64 +"@esbuild/openharmony-arm64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/openharmony-arm64@npm:0.27.1" + conditions: os=openharmony & cpu=arm64 languageName: node linkType: hard @@ -8884,10 +9067,10 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/win32-arm64@npm:0.21.5" - conditions: os=win32 & cpu=arm64 +"@esbuild/sunos-x64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/sunos-x64@npm:0.27.1" + conditions: os=sunos & cpu=x64 languageName: node linkType: hard @@ -8898,10 +9081,10 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/win32-ia32@npm:0.21.5" - conditions: os=win32 & cpu=ia32 +"@esbuild/win32-arm64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/win32-arm64@npm:0.27.1" + conditions: os=win32 & cpu=arm64 languageName: node linkType: hard @@ -8912,10 +9095,10 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/win32-x64@npm:0.21.5" - conditions: os=win32 & cpu=x64 +"@esbuild/win32-ia32@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/win32-ia32@npm:0.27.1" + conditions: os=win32 & cpu=ia32 languageName: node linkType: hard @@ -8926,14 +9109,21 @@ __metadata: languageName: node linkType: hard -"@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": - version: 4.4.0 - resolution: "@eslint-community/eslint-utils@npm:4.4.0" +"@esbuild/win32-x64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/win32-x64@npm:0.27.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0, @eslint-community/eslint-utils@npm:^4.7.0": + version: 4.9.0 + resolution: "@eslint-community/eslint-utils@npm:4.9.0" dependencies: - eslint-visitor-keys: "npm:^3.3.0" + eslint-visitor-keys: "npm:^3.4.3" peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - checksum: 10/8d70bcdcd8cd279049183aca747d6c2ed7092a5cf0cf5916faac1ef37ffa74f0c245c2a3a3d3b9979d9dfdd4ca59257b4c5621db699d637b847a2c5e02f491c2 + checksum: 10/89b1eb3137e14c379865e60573f524fcc0ee5c4b0c7cd21090673e75e5a720f14b92f05ab2d02704c2314b67e67b6f96f3bb209ded6b890ced7b667aa4bf1fa2 languageName: node linkType: hard @@ -8975,10 +9165,10 @@ __metadata: languageName: node linkType: hard -"@faker-js/faker@npm:^8.4.1": - version: 8.4.1 - resolution: "@faker-js/faker@npm:8.4.1" - checksum: 10/5983c2ea64f26055ad6648de748878e11ebe2fb751e3c7435ae141cdffabc2dccfe4c4f49da69a3d2add71e21b415c683ac5fba196fab0d5ed6779fbec436c80 +"@faker-js/faker@npm:^10.0.0": + version: 10.1.0 + resolution: "@faker-js/faker@npm:10.1.0" + checksum: 10/3332795debec5793dfaea79e740ed44bd7d94fb43cea8b1bd5d59af19f15c4a9533fa696cfebf024a9bba5d9d1a8a28cde40a92670c03cc11170a3025e551e65 languageName: node linkType: hard @@ -9085,16 +9275,16 @@ __metadata: languageName: node linkType: hard -"@gerrit0/mini-shiki@npm:^3.12.0": - version: 3.14.0 - resolution: "@gerrit0/mini-shiki@npm:3.14.0" +"@gerrit0/mini-shiki@npm:^3.17.0": + version: 3.17.1 + resolution: "@gerrit0/mini-shiki@npm:3.17.1" dependencies: - "@shikijs/engine-oniguruma": "npm:^3.14.0" - "@shikijs/langs": "npm:^3.14.0" - "@shikijs/themes": "npm:^3.14.0" - "@shikijs/types": "npm:^3.14.0" + "@shikijs/engine-oniguruma": "npm:^3.17.1" + "@shikijs/langs": "npm:^3.17.1" + "@shikijs/themes": "npm:^3.17.1" + "@shikijs/types": "npm:^3.17.1" "@shikijs/vscode-textmate": "npm:^10.0.2" - checksum: 10/339fd706f2bf30193e87fe25e4d2bb676c7fd85f0abddf859c055d9ffdbfc9c5a7cd113555e990be409fb115ee24fbcb335b17e74fa71903430b4e3a619379b1 + checksum: 10/efcf8bea95911dc645c76b90aba2ed0898efa42ec068774c77f5272343aa421dbb72b08f7ce40df90f3223e56c21af185021a81b43a34bd73be248100dc3a21c languageName: node linkType: hard @@ -9165,14 +9355,14 @@ __metadata: linkType: hard "@google-cloud/cloud-sql-connector@npm:^1.4.0": - version: 1.8.4 - resolution: "@google-cloud/cloud-sql-connector@npm:1.8.4" + version: 1.8.5 + resolution: "@google-cloud/cloud-sql-connector@npm:1.8.5" dependencies: "@googleapis/sqladmin": "npm:^31.1.0" - gaxios: "npm:^7.1.2" - google-auth-library: "npm:^10.4.0" + gaxios: "npm:^7.1.3" + google-auth-library: "npm:^10.5.0" p-throttle: "npm:^7.0.0" - checksum: 10/39d68ed3d6c65eff4af198711c723d6c82ec529fb6a2f7a5e2457f5b12731103796d27b9ce788b3326f710ffbb642147b1e4a5b7e83ff2109fcc119da7f601e9 + checksum: 10/38be5d2a32d68d2c3ef8b56c94c9eda299266a742fa2beecb9e907f27117bf1af25af5881195fa21da167b12dfdd22f55662086e90448b8c02824875eeb2cc85 languageName: node linkType: hard @@ -9252,8 +9442,8 @@ __metadata: linkType: hard "@google-cloud/storage@npm:^7.0.0": - version: 7.17.3 - resolution: "@google-cloud/storage@npm:7.17.3" + version: 7.18.0 + resolution: "@google-cloud/storage@npm:7.18.0" dependencies: "@google-cloud/paginator": "npm:^5.0.0" "@google-cloud/projectify": "npm:^4.0.0" @@ -9270,7 +9460,7 @@ __metadata: retry-request: "npm:^7.0.0" teeny-request: "npm:^9.0.0" uuid: "npm:^8.0.0" - checksum: 10/e38069a08541757781d7f5ae3f7633e78b4e9b5be1937a7f8f3a4df09c0f6b6b2746ade419ec0e4ffe751ddcc6eb9069cadfc14bbc869edcc4cbc0b1bf6adb4a + checksum: 10/29ca208cf88770dd60ac7868ec69bffa43e3cba03cbb49dd7064593f777e66181a8cba6f1993f36193f009be07bc02129d0ecac9f9f09f8a8b6a8d90a9e5ba86 languageName: node linkType: hard @@ -9789,18 +9979,18 @@ __metadata: languageName: node linkType: hard -"@inquirer/external-editor@npm:^1.0.0": - version: 1.0.2 - resolution: "@inquirer/external-editor@npm:1.0.2" +"@inquirer/external-editor@npm:^1.0.0, @inquirer/external-editor@npm:^1.0.2": + version: 1.0.3 + resolution: "@inquirer/external-editor@npm:1.0.3" dependencies: - chardet: "npm:^2.1.0" + chardet: "npm:^2.1.1" iconv-lite: "npm:^0.7.0" peerDependencies: "@types/node": ">=18" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/d0c5c73249b8153f4cf872c4fba01c57a7653142a4cad496f17ed03ef3769330a4b3c519b68d70af69d4bb33003d2599b66b2242be85411c0b027ff383619666 + checksum: 10/c95d7237a885b32031715089f92820525731d4d3c2bd7afdb826307dc296cc2b39e7a644b0bb265441963348cca42e7785feb29c3aaf18fd2b63131769bf6587 languageName: node linkType: hard @@ -9833,7 +10023,6 @@ __metadata: "@backstage/version-bridge": "workspace:^" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" - zod: "npm:^3.22.4" languageName: unknown linkType: soft @@ -9858,7 +10047,7 @@ __metadata: "@backstage/errors": "workspace:^" "@types/express": "npm:^4.17.6" "@types/supertest": "npm:^2.0.8" - express: "npm:^4.17.1" + express: "npm:^4.22.0" express-promise-router: "npm:^4.1.0" supertest: "npm:^7.0.0" uuid: "npm:^11.0.0" @@ -10068,10 +10257,10 @@ __metadata: languageName: node linkType: hard -"@istanbuljs/schema@npm:^0.1.2": - version: 0.1.2 - resolution: "@istanbuljs/schema@npm:0.1.2" - checksum: 10/e4a7fffc72fb2cfe2edfee8a09f68b2da18b1ab328a29d8be2933681f9e36103f0b083a5ae07129f4de26296eb7c2a1cc144cfea6cdb47f871a69766947144d3 +"@istanbuljs/schema@npm:^0.1.2, @istanbuljs/schema@npm:^0.1.3": + version: 0.1.3 + resolution: "@istanbuljs/schema@npm:0.1.3" + checksum: 10/a9b1e49acdf5efc2f5b2359f2df7f90c5c725f2656f16099e8b2cd3a000619ecca9fc48cf693ba789cf0fd989f6e0df6a22bc05574be4223ecdbb7997d04384b languageName: node linkType: hard @@ -10084,161 +10273,214 @@ __metadata: languageName: node linkType: hard -"@jest/console@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/console@npm:29.7.0" +"@jest/console@npm:30.2.0": + version: 30.2.0 + resolution: "@jest/console@npm:30.2.0" dependencies: - "@jest/types": "npm:^29.6.3" + "@jest/types": "npm:30.2.0" "@types/node": "npm:*" - chalk: "npm:^4.0.0" - jest-message-util: "npm:^29.7.0" - jest-util: "npm:^29.7.0" + chalk: "npm:^4.1.2" + jest-message-util: "npm:30.2.0" + jest-util: "npm:30.2.0" slash: "npm:^3.0.0" - checksum: 10/4a80c750e8a31f344233cb9951dee9b77bf6b89377cb131f8b3cde07ff218f504370133a5963f6a786af4d2ce7f85642db206ff7a15f99fe58df4c38ac04899e + checksum: 10/7cda9793962afa5c7fcfdde0ff5012694683b17941ee3c6a55ea9fd9a02f1c51ec4b4c767b867e1226f85a26af1d0f0d72c6a344e34c5bc4300312ebffd6e50b languageName: node linkType: hard -"@jest/core@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/core@npm:29.7.0" +"@jest/core@npm:30.2.0": + version: 30.2.0 + resolution: "@jest/core@npm:30.2.0" dependencies: - "@jest/console": "npm:^29.7.0" - "@jest/reporters": "npm:^29.7.0" - "@jest/test-result": "npm:^29.7.0" - "@jest/transform": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" + "@jest/console": "npm:30.2.0" + "@jest/pattern": "npm:30.0.1" + "@jest/reporters": "npm:30.2.0" + "@jest/test-result": "npm:30.2.0" + "@jest/transform": "npm:30.2.0" + "@jest/types": "npm:30.2.0" "@types/node": "npm:*" - ansi-escapes: "npm:^4.2.1" - chalk: "npm:^4.0.0" - ci-info: "npm:^3.2.0" - exit: "npm:^0.1.2" - graceful-fs: "npm:^4.2.9" - jest-changed-files: "npm:^29.7.0" - jest-config: "npm:^29.7.0" - jest-haste-map: "npm:^29.7.0" - jest-message-util: "npm:^29.7.0" - jest-regex-util: "npm:^29.6.3" - jest-resolve: "npm:^29.7.0" - jest-resolve-dependencies: "npm:^29.7.0" - jest-runner: "npm:^29.7.0" - jest-runtime: "npm:^29.7.0" - jest-snapshot: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - jest-validate: "npm:^29.7.0" - jest-watcher: "npm:^29.7.0" - micromatch: "npm:^4.0.4" - pretty-format: "npm:^29.7.0" + ansi-escapes: "npm:^4.3.2" + chalk: "npm:^4.1.2" + ci-info: "npm:^4.2.0" + exit-x: "npm:^0.2.2" + graceful-fs: "npm:^4.2.11" + jest-changed-files: "npm:30.2.0" + jest-config: "npm:30.2.0" + jest-haste-map: "npm:30.2.0" + jest-message-util: "npm:30.2.0" + jest-regex-util: "npm:30.0.1" + jest-resolve: "npm:30.2.0" + jest-resolve-dependencies: "npm:30.2.0" + jest-runner: "npm:30.2.0" + jest-runtime: "npm:30.2.0" + jest-snapshot: "npm:30.2.0" + jest-util: "npm:30.2.0" + jest-validate: "npm:30.2.0" + jest-watcher: "npm:30.2.0" + micromatch: "npm:^4.0.8" + pretty-format: "npm:30.2.0" slash: "npm:^3.0.0" - strip-ansi: "npm:^6.0.0" peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 peerDependenciesMeta: node-notifier: optional: true - checksum: 10/ab6ac2e562d083faac7d8152ec1cc4eccc80f62e9579b69ed40aedf7211a6b2d57024a6cd53c4e35fd051c39a236e86257d1d99ebdb122291969a0a04563b51e + checksum: 10/6763bb1efd937778f009821cd94c3705d3c31a156258a224b8745c1e0887976683f5413745ffb361b526f0fa2692e36aaa963aa197cc77ba932cff9d6d28af9d languageName: node linkType: hard -"@jest/create-cache-key-function@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/create-cache-key-function@npm:29.7.0" +"@jest/create-cache-key-function@npm:^30.0.0": + version: 30.2.0 + resolution: "@jest/create-cache-key-function@npm:30.2.0" dependencies: - "@jest/types": "npm:^29.6.3" - checksum: 10/061ef63b13ec8c8e5d08e4456f03b5cf8c7f9c1cab4fed8402e1479153cafce6eea80420e308ef62027abb7e29b825fcfa06551856bd021d98e92e381bf91723 + "@jest/types": "npm:30.2.0" + checksum: 10/7a2dd0efe747c1b2e61825e51ace11e42f278203fae37a3b9462c8d2132394978682ed7094f5ce3d9f5a9e5f2855e6d1d933e5f3ac5165b127c36591f3d98d85 languageName: node linkType: hard -"@jest/environment@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/environment@npm:29.7.0" +"@jest/diff-sequences@npm:30.0.1": + version: 30.0.1 + resolution: "@jest/diff-sequences@npm:30.0.1" + checksum: 10/0ddb7c7ba92d6057a2ee51a9cfc2155b77cca707fe959167466ea02dcb0687018cc3c22b9622f25f3a417d6ad370e2d4dcfedf9f1410dc9c02954a7484423cc7 + languageName: node + linkType: hard + +"@jest/environment-jsdom-abstract@npm:^30.0.0": + version: 30.2.0 + resolution: "@jest/environment-jsdom-abstract@npm:30.2.0" dependencies: - "@jest/fake-timers": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" + "@jest/environment": "npm:30.2.0" + "@jest/fake-timers": "npm:30.2.0" + "@jest/types": "npm:30.2.0" + "@types/jsdom": "npm:^21.1.7" "@types/node": "npm:*" - jest-mock: "npm:^29.7.0" - checksum: 10/90b5844a9a9d8097f2cf107b1b5e57007c552f64315da8c1f51217eeb0a9664889d3f145cdf8acf23a84f4d8309a6675e27d5b059659a004db0ea9546d1c81a8 + jest-mock: "npm:30.2.0" + jest-util: "npm:30.2.0" + peerDependencies: + canvas: ^3.0.0 + jsdom: "*" + peerDependenciesMeta: + canvas: + optional: true + checksum: 10/65a9c8504f213f4d125956383ffe6c4e566cfb0ff2fe67783adf9ebde33f772339e61fdd98ddc2bbae3029e3356d2386abedb9d101aa95d6fd51fabac38bebe0 languageName: node linkType: hard -"@jest/expect-utils@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/expect-utils@npm:29.7.0" +"@jest/environment@npm:30.2.0": + version: 30.2.0 + resolution: "@jest/environment@npm:30.2.0" dependencies: - jest-get-type: "npm:^29.6.3" - checksum: 10/ef8d379778ef574a17bde2801a6f4469f8022a46a5f9e385191dc73bb1fc318996beaed4513fbd7055c2847227a1bed2469977821866534593a6e52a281499ee - languageName: node - linkType: hard - -"@jest/expect@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/expect@npm:29.7.0" - dependencies: - expect: "npm:^29.7.0" - jest-snapshot: "npm:^29.7.0" - checksum: 10/fea6c3317a8da5c840429d90bfe49d928e89c9e89fceee2149b93a11b7e9c73d2f6e4d7cdf647163da938fc4e2169e4490be6bae64952902bc7a701033fd4880 - languageName: node - linkType: hard - -"@jest/fake-timers@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/fake-timers@npm:29.7.0" - dependencies: - "@jest/types": "npm:^29.6.3" - "@sinonjs/fake-timers": "npm:^10.0.2" + "@jest/fake-timers": "npm:30.2.0" + "@jest/types": "npm:30.2.0" "@types/node": "npm:*" - jest-message-util: "npm:^29.7.0" - jest-mock: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - checksum: 10/9b394e04ffc46f91725ecfdff34c4e043eb7a16e1d78964094c9db3fde0b1c8803e45943a980e8c740d0a3d45661906de1416ca5891a538b0660481a3a828c27 + jest-mock: "npm:30.2.0" + checksum: 10/e168a4ff328980eb9fde5e43aea80807fd0b2dbd4579ae8f68a03415a1e58adf5661db298054fa2351c7cb2b5a74bf67b8ab996656cf5927d0b0d0b6e2c2966b languageName: node linkType: hard -"@jest/globals@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/globals@npm:29.7.0" +"@jest/expect-utils@npm:30.2.0": + version: 30.2.0 + resolution: "@jest/expect-utils@npm:30.2.0" dependencies: - "@jest/environment": "npm:^29.7.0" - "@jest/expect": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - jest-mock: "npm:^29.7.0" - checksum: 10/97dbb9459135693ad3a422e65ca1c250f03d82b2a77f6207e7fa0edd2c9d2015fbe4346f3dc9ebff1678b9d8da74754d4d440b7837497f8927059c0642a22123 + "@jest/get-type": "npm:30.1.0" + checksum: 10/f2442f1bceb3411240d0f16fd0074377211b4373d3b8b2dc28929e861b6527a6deb403a362c25afa511d933cda4dfbdc98d4a08eeb51ee4968f7cb0299562349 languageName: node linkType: hard -"@jest/reporters@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/reporters@npm:29.7.0" +"@jest/expect@npm:30.2.0": + version: 30.2.0 + resolution: "@jest/expect@npm:30.2.0" + dependencies: + expect: "npm:30.2.0" + jest-snapshot: "npm:30.2.0" + checksum: 10/d950d95a64d5c6a39d56171dabb8dbe59423096231bb4f21d8ee0019878e6626701ac9d782803dc2589e2799ed39704031f818533f8a3e571b57032eafa85d12 + languageName: node + linkType: hard + +"@jest/fake-timers@npm:30.2.0": + version: 30.2.0 + resolution: "@jest/fake-timers@npm:30.2.0" + dependencies: + "@jest/types": "npm:30.2.0" + "@sinonjs/fake-timers": "npm:^13.0.0" + "@types/node": "npm:*" + jest-message-util: "npm:30.2.0" + jest-mock: "npm:30.2.0" + jest-util: "npm:30.2.0" + checksum: 10/c2df66576ba8049b07d5f239777243e21fcdaa09a446be1e55fac709d6273e2a926c1562e0372c3013142557ed9d386381624023549267a667b6e1b656e37fe6 + languageName: node + linkType: hard + +"@jest/get-type@npm:30.1.0": + version: 30.1.0 + resolution: "@jest/get-type@npm:30.1.0" + checksum: 10/e2a95fbb49ce2d15547db8af5602626caf9b05f62a5e583b4a2de9bd93a2bfe7175f9bbb2b8a5c3909ce261d467b6991d7265bb1d547cb60e7e97f571f361a70 + languageName: node + linkType: hard + +"@jest/globals@npm:30.2.0": + version: 30.2.0 + resolution: "@jest/globals@npm:30.2.0" + dependencies: + "@jest/environment": "npm:30.2.0" + "@jest/expect": "npm:30.2.0" + "@jest/types": "npm:30.2.0" + jest-mock: "npm:30.2.0" + checksum: 10/d4a331d3847cebb3acefe120350d8a6bb5517c1403de7cd2b4dc67be425f37ba0511beee77d6837b4da2d93a25a06d6f829ad7837da365fae45e1da57523525c + languageName: node + linkType: hard + +"@jest/pattern@npm:30.0.1": + version: 30.0.1 + resolution: "@jest/pattern@npm:30.0.1" + dependencies: + "@types/node": "npm:*" + jest-regex-util: "npm:30.0.1" + checksum: 10/afd03b4d3eadc9c9970cf924955dee47984a7e767901fe6fa463b17b246f0ddeec07b3e82c09715c54bde3c8abb92074160c0d79967bd23778724f184e7f5b7b + languageName: node + linkType: hard + +"@jest/reporters@npm:30.2.0": + version: 30.2.0 + resolution: "@jest/reporters@npm:30.2.0" dependencies: "@bcoe/v8-coverage": "npm:^0.2.3" - "@jest/console": "npm:^29.7.0" - "@jest/test-result": "npm:^29.7.0" - "@jest/transform": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - "@jridgewell/trace-mapping": "npm:^0.3.18" + "@jest/console": "npm:30.2.0" + "@jest/test-result": "npm:30.2.0" + "@jest/transform": "npm:30.2.0" + "@jest/types": "npm:30.2.0" + "@jridgewell/trace-mapping": "npm:^0.3.25" "@types/node": "npm:*" - chalk: "npm:^4.0.0" - collect-v8-coverage: "npm:^1.0.0" - exit: "npm:^0.1.2" - glob: "npm:^7.1.3" - graceful-fs: "npm:^4.2.9" + chalk: "npm:^4.1.2" + collect-v8-coverage: "npm:^1.0.2" + exit-x: "npm:^0.2.2" + glob: "npm:^10.3.10" + graceful-fs: "npm:^4.2.11" istanbul-lib-coverage: "npm:^3.0.0" istanbul-lib-instrument: "npm:^6.0.0" istanbul-lib-report: "npm:^3.0.0" - istanbul-lib-source-maps: "npm:^4.0.0" + istanbul-lib-source-maps: "npm:^5.0.0" istanbul-reports: "npm:^3.1.3" - jest-message-util: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - jest-worker: "npm:^29.7.0" + jest-message-util: "npm:30.2.0" + jest-util: "npm:30.2.0" + jest-worker: "npm:30.2.0" slash: "npm:^3.0.0" - string-length: "npm:^4.0.1" - strip-ansi: "npm:^6.0.0" + string-length: "npm:^4.0.2" v8-to-istanbul: "npm:^9.0.1" peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 peerDependenciesMeta: node-notifier: optional: true - checksum: 10/a17d1644b26dea14445cedd45567f4ba7834f980be2ef74447204e14238f121b50d8b858fde648083d2cd8f305f81ba434ba49e37a5f4237a6f2a61180cc73dc + checksum: 10/3848b59bf740c10c4e5c234dcc41c54adbd74932bf05d1d1582d09d86e9baa86ddaf3c43903505fd042ba1203c2889a732137d08058ce9dc0069ba33b5d5373d + languageName: node + linkType: hard + +"@jest/schemas@npm:30.0.5": + version: 30.0.5 + resolution: "@jest/schemas@npm:30.0.5" + dependencies: + "@sinclair/typebox": "npm:^0.34.0" + checksum: 10/40df4db55d4aeed09d1c7e19caf23788309cea34490a1c5d584c913494195e698b9967e996afc27226cac6d76e7512fe73ae6b9584480695c60dd18a5459cdba languageName: node linkType: hard @@ -10251,61 +10493,88 @@ __metadata: languageName: node linkType: hard -"@jest/source-map@npm:^29.6.3": - version: 29.6.3 - resolution: "@jest/source-map@npm:29.6.3" +"@jest/snapshot-utils@npm:30.2.0": + version: 30.2.0 + resolution: "@jest/snapshot-utils@npm:30.2.0" dependencies: - "@jridgewell/trace-mapping": "npm:^0.3.18" - callsites: "npm:^3.0.0" - graceful-fs: "npm:^4.2.9" - checksum: 10/bcc5a8697d471396c0003b0bfa09722c3cd879ad697eb9c431e6164e2ea7008238a01a07193dfe3cbb48b1d258eb7251f6efcea36f64e1ebc464ea3c03ae2deb + "@jest/types": "npm:30.2.0" + chalk: "npm:^4.1.2" + graceful-fs: "npm:^4.2.11" + natural-compare: "npm:^1.4.0" + checksum: 10/6b30ab2b0682117e3ce775e70b5be1eb01e1ea53a74f12ac7090cd1a5f37e9b795cd8de83853afa7b4b799c96b1c482499aa993ca2034ea0679525d32b7f9625 languageName: node linkType: hard -"@jest/test-result@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/test-result@npm:29.7.0" +"@jest/source-map@npm:30.0.1": + version: 30.0.1 + resolution: "@jest/source-map@npm:30.0.1" dependencies: - "@jest/console": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - "@types/istanbul-lib-coverage": "npm:^2.0.0" - collect-v8-coverage: "npm:^1.0.0" - checksum: 10/c073ab7dfe3c562bff2b8fee6cc724ccc20aa96bcd8ab48ccb2aa309b4c0c1923a9e703cea386bd6ae9b71133e92810475bb9c7c22328fc63f797ad3324ed189 + "@jridgewell/trace-mapping": "npm:^0.3.25" + callsites: "npm:^3.1.0" + graceful-fs: "npm:^4.2.11" + checksum: 10/161b27cdf8d9d80fd99374d55222b90478864c6990514be6ebee72b7184a034224c9aceed12c476f3a48d48601bf8ed2e0c047a5a81bd907dc192ebe71365ed4 languageName: node linkType: hard -"@jest/test-sequencer@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/test-sequencer@npm:29.7.0" +"@jest/test-result@npm:30.2.0": + version: 30.2.0 + resolution: "@jest/test-result@npm:30.2.0" dependencies: - "@jest/test-result": "npm:^29.7.0" - graceful-fs: "npm:^4.2.9" - jest-haste-map: "npm:^29.7.0" + "@jest/console": "npm:30.2.0" + "@jest/types": "npm:30.2.0" + "@types/istanbul-lib-coverage": "npm:^2.0.6" + collect-v8-coverage: "npm:^1.0.2" + checksum: 10/f58f79c3c3ba6dd15325e05b0b5a300777cd8cc38327f622608b6fe849b1073ee9633e33d1e5d7ef5b97a1ce71543d0ad92674b7a279f53033143e8dd7c22959 + languageName: node + linkType: hard + +"@jest/test-sequencer@npm:30.2.0": + version: 30.2.0 + resolution: "@jest/test-sequencer@npm:30.2.0" + dependencies: + "@jest/test-result": "npm:30.2.0" + graceful-fs: "npm:^4.2.11" + jest-haste-map: "npm:30.2.0" slash: "npm:^3.0.0" - checksum: 10/4420c26a0baa7035c5419b0892ff8ffe9a41b1583ec54a10db3037cd46a7e29dd3d7202f8aa9d376e9e53be5f8b1bc0d16e1de6880a6d319b033b01dc4c8f639 + checksum: 10/7923964b27048b2233858b32aa1b34d4dd9e404311626d944a706bcdcaa0b1585f43f2ffa3fa893ecbf133566f31ba2b79ab5eaaaf674b8558c6c7029ecbea5e languageName: node linkType: hard -"@jest/transform@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/transform@npm:29.7.0" +"@jest/transform@npm:30.2.0": + version: 30.2.0 + resolution: "@jest/transform@npm:30.2.0" dependencies: - "@babel/core": "npm:^7.11.6" - "@jest/types": "npm:^29.6.3" - "@jridgewell/trace-mapping": "npm:^0.3.18" - babel-plugin-istanbul: "npm:^6.1.1" - chalk: "npm:^4.0.0" + "@babel/core": "npm:^7.27.4" + "@jest/types": "npm:30.2.0" + "@jridgewell/trace-mapping": "npm:^0.3.25" + babel-plugin-istanbul: "npm:^7.0.1" + chalk: "npm:^4.1.2" convert-source-map: "npm:^2.0.0" fast-json-stable-stringify: "npm:^2.1.0" - graceful-fs: "npm:^4.2.9" - jest-haste-map: "npm:^29.7.0" - jest-regex-util: "npm:^29.6.3" - jest-util: "npm:^29.7.0" - micromatch: "npm:^4.0.4" - pirates: "npm:^4.0.4" + graceful-fs: "npm:^4.2.11" + jest-haste-map: "npm:30.2.0" + jest-regex-util: "npm:30.0.1" + jest-util: "npm:30.2.0" + micromatch: "npm:^4.0.8" + pirates: "npm:^4.0.7" slash: "npm:^3.0.0" - write-file-atomic: "npm:^4.0.2" - checksum: 10/30f42293545ab037d5799c81d3e12515790bb58513d37f788ce32d53326d0d72ebf5b40f989e6896739aa50a5f77be44686e510966370d58511d5ad2637c68c1 + write-file-atomic: "npm:^5.0.1" + checksum: 10/c75d72d524c2a50ea6c05778a9b76a6e48bc228a3390896a6edd4416f7b4954ee0a07e229ed7b4949ce8889324b70034c784751e3fc455a25648bd8dcad17d0d + languageName: node + linkType: hard + +"@jest/types@npm:30.2.0": + version: 30.2.0 + resolution: "@jest/types@npm:30.2.0" + dependencies: + "@jest/pattern": "npm:30.0.1" + "@jest/schemas": "npm:30.0.5" + "@types/istanbul-lib-coverage": "npm:^2.0.6" + "@types/istanbul-reports": "npm:^3.0.4" + "@types/node": "npm:*" + "@types/yargs": "npm:^17.0.33" + chalk: "npm:^4.1.2" + checksum: 10/f50fcaea56f873a51d19254ab16762f2ea8ca88e3e08da2e496af5da2b67c322915a4fcd0153803cc05063ffe87ebef2ab4330e0a1b06ab984a26c916cbfc26b languageName: node linkType: hard @@ -10323,12 +10592,11 @@ __metadata: languageName: node linkType: hard -"@joshwooding/vite-plugin-react-docgen-typescript@npm:0.6.1": - version: 0.6.1 - resolution: "@joshwooding/vite-plugin-react-docgen-typescript@npm:0.6.1" +"@joshwooding/vite-plugin-react-docgen-typescript@npm:^0.6.3": + version: 0.6.3 + resolution: "@joshwooding/vite-plugin-react-docgen-typescript@npm:0.6.3" dependencies: - glob: "npm:^10.0.0" - magic-string: "npm:^0.30.0" + glob: "npm:^11.1.0" react-docgen-typescript: "npm:^2.2.2" peerDependencies: typescript: ">= 4.3.x" @@ -10336,7 +10604,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10/a82b6005378ccda13250fcfeaa04ec2ba17d1c2923b5bba5907d5b2fd658c661b29c1215eb1c0fe305b390bee89ec77c684aa68506262734c3bb3cd76d8a6963 + checksum: 10/8a0f2c52b99e0dff4eeee574cf373b794e5398ee3c6b4d272378ccfe3b7dc2f3dc647d446716998ee2d88ee26df617e7b10da949702edcb625ee49e93b7daa95 languageName: node linkType: hard @@ -10377,7 +10645,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.15, @jridgewell/sourcemap-codec@npm:^1.5.0": +"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.15, @jridgewell/sourcemap-codec@npm:^1.5.0, @jridgewell/sourcemap-codec@npm:^1.5.5": version: 1.5.5 resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" checksum: 10/5d9d207b462c11e322d71911e55e21a4e2772f71ffe8d6f1221b8eb5ae6774458c1d242f897fb0814e8714ca9a6b498abfa74dfe4f434493342902b1a48b33a5 @@ -10394,13 +10662,13 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.28, @jridgewell/trace-mapping@npm:^0.3.9": - version: 0.3.30 - resolution: "@jridgewell/trace-mapping@npm:0.3.30" +"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.23, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.28, @jridgewell/trace-mapping@npm:^0.3.9": + version: 0.3.31 + resolution: "@jridgewell/trace-mapping@npm:0.3.31" dependencies: "@jridgewell/resolve-uri": "npm:^3.1.0" "@jridgewell/sourcemap-codec": "npm:^1.4.14" - checksum: 10/f9fabe1122058f4fedc242bdc43fcaacb6b222c6fb712b7904c37704dcb16e50e07ca137ff99043da44292b18a8720296ff97f7703e960678b8ef51d0db4d250 + checksum: 10/da0283270e691bdb5543806077548532791608e52386cfbbf3b9e8fb00457859d1bd01d512851161c886eb3a2f3ce6fd9bcf25db8edf3bddedd275bd4a88d606 languageName: node linkType: hard @@ -10454,12 +10722,12 @@ __metadata: languageName: node linkType: hard -"@jsonjoy.com/buffers@npm:^1.0.0": - version: 1.0.0 - resolution: "@jsonjoy.com/buffers@npm:1.0.0" +"@jsonjoy.com/buffers@npm:^1.0.0, @jsonjoy.com/buffers@npm:^1.2.0": + version: 1.2.1 + resolution: "@jsonjoy.com/buffers@npm:1.2.1" peerDependencies: tslib: 2 - checksum: 10/3347a16a555398c19265203877b8e325be5facc1d875c0e85db0cc20b53418302257ed5af15bf53b84ff8337bd024a10be25ff3a02ebc54ae8e643577ca29e63 + checksum: 10/8ef4784d05c0fb4d0f27a1f78f5b0ae1f3b537d237f978d10be0b88f59a534ae44db2a4bde28eee0eb461ede31dc194aab5927ac001ed2b764629fa43ae9b60b languageName: node linkType: hard @@ -10472,35 +10740,37 @@ __metadata: languageName: node linkType: hard -"@jsonjoy.com/json-pack@npm:^1.0.3": - version: 1.10.0 - resolution: "@jsonjoy.com/json-pack@npm:1.10.0" +"@jsonjoy.com/json-pack@npm:^1.11.0": + version: 1.21.0 + resolution: "@jsonjoy.com/json-pack@npm:1.21.0" dependencies: "@jsonjoy.com/base64": "npm:^1.1.2" - "@jsonjoy.com/buffers": "npm:^1.0.0" + "@jsonjoy.com/buffers": "npm:^1.2.0" "@jsonjoy.com/codegen": "npm:^1.0.0" - "@jsonjoy.com/json-pointer": "npm:^1.0.1" + "@jsonjoy.com/json-pointer": "npm:^1.0.2" "@jsonjoy.com/util": "npm:^1.9.0" hyperdyperid: "npm:^1.2.0" thingies: "npm:^2.5.0" + tree-dump: "npm:^1.1.0" peerDependencies: tslib: 2 - checksum: 10/5ad1eb008cf5f681dbc1301f5a7c33ca3a101d22773dbd2a9db4b02e5847c2f8b0c5f074c69f4ced5c24b3dc1dd85ddbc1688f0002fee9416869c579c34c4a8e + checksum: 10/138b7eb8c96e6e435b0218c8f2eb5554e4eb49198a8718673a65e81da53b4617553ffa7124b51d6ea00fdfb868d6ff8b5ad6365e8336380ca7025f04d0412ee7 languageName: node linkType: hard -"@jsonjoy.com/json-pointer@npm:^1.0.1": - version: 1.0.1 - resolution: "@jsonjoy.com/json-pointer@npm:1.0.1" +"@jsonjoy.com/json-pointer@npm:^1.0.2": + version: 1.0.2 + resolution: "@jsonjoy.com/json-pointer@npm:1.0.2" dependencies: - "@jsonjoy.com/util": "npm:^1.3.0" + "@jsonjoy.com/codegen": "npm:^1.0.0" + "@jsonjoy.com/util": "npm:^1.9.0" peerDependencies: tslib: 2 - checksum: 10/ff15df95df38677a09fe2155cf4521844c9f0653c77a7090bb6e726d275fb78ab6c3543c4ebdc14aff07e44da08b1545631a63e42e217c18059194957e060ed9 + checksum: 10/f22baeb3abc8ace2d8902d06ec297343431d4486dcf399aaaffd26ace7e62e194fe0efb4b7880e45b3b7939224ee838d3213448ef654fc8a61c91a76fe994d94 languageName: node linkType: hard -"@jsonjoy.com/util@npm:^1.3.0, @jsonjoy.com/util@npm:^1.9.0": +"@jsonjoy.com/util@npm:^1.9.0": version: 1.9.0 resolution: "@jsonjoy.com/util@npm:1.9.0" dependencies: @@ -10545,12 +10815,10 @@ __metadata: languageName: node linkType: hard -"@keyv/serialize@npm:^1.0.3": - version: 1.0.3 - resolution: "@keyv/serialize@npm:1.0.3" - dependencies: - buffer: "npm:^6.0.3" - checksum: 10/d6a9194dd781bc26cc4d55f392d843810c1fdc0da81e69203e633cb289fc0a8edc8bc6466f66c4cbb55da0a5b405e89f14a68b48d6e73919ae82f8249fb5e444 +"@keyv/serialize@npm:^1.0.3, @keyv/serialize@npm:^1.1.1": + version: 1.1.1 + resolution: "@keyv/serialize@npm:1.1.1" + checksum: 10/e3b2cb1377863342acedd5ff785af3e69269bee9b44707c617d1c8bc14eeb5ac763159d6455903ffe92f143c2238e1e783c4f113f9c8910eacccf172894472da languageName: node linkType: hard @@ -10563,7 +10831,7 @@ __metadata: languageName: node linkType: hard -"@kubernetes-models/apimachinery@npm:^2.0.0, @kubernetes-models/apimachinery@npm:^2.0.2": +"@kubernetes-models/apimachinery@npm:^2.0.0, @kubernetes-models/apimachinery@npm:^2.2.0": version: 2.2.0 resolution: "@kubernetes-models/apimachinery@npm:2.2.0" dependencies: @@ -10634,28 +10902,28 @@ __metadata: languageName: node linkType: hard -"@lezer/common@npm:^1.0.0, @lezer/common@npm:^1.1.0": - version: 1.1.0 - resolution: "@lezer/common@npm:1.1.0" - checksum: 10/cabe34758bb41c6c7e38aaabdc8a6f51469b1307ed9b5760dd1fc7777d77a012e3e3f37c970a91319c10cc5e4d355db5c0e5515cc9392b9d0b833a66a8cc4120 +"@lezer/common@npm:^1.0.0, @lezer/common@npm:^1.1.0, @lezer/common@npm:^1.3.0": + version: 1.4.0 + resolution: "@lezer/common@npm:1.4.0" + checksum: 10/8d0626835b8567115923772619d887c212c1e965e0ae2a230e3ae8e6734e0ded3bf4ca3a1eb4aa0af6a7f0cbb4892fa04de399916192fd98a99db8bf0f18da63 languageName: node linkType: hard "@lezer/highlight@npm:^1.0.0": - version: 1.0.0 - resolution: "@lezer/highlight@npm:1.0.0" + version: 1.2.3 + resolution: "@lezer/highlight@npm:1.2.3" dependencies: - "@lezer/common": "npm:^1.0.0" - checksum: 10/ed362dd4c27218bd941807f14ee691f1ad73613f96041538f5d2cc06521490a346a74044f32da573c1c6529bea2f5c190064c4bca92918732cb580c298880947 + "@lezer/common": "npm:^1.3.0" + checksum: 10/8f787d464f8a036f117a0b23e73ac034d224a57d72501c6559089098a28f127c9e495b90ac7d132acc86199e0b64d4c038f75f9293a37c7c61add52fa1acdb4e languageName: node linkType: hard "@lezer/lr@npm:^1.0.0": - version: 1.0.0 - resolution: "@lezer/lr@npm:1.0.0" + version: 1.4.4 + resolution: "@lezer/lr@npm:1.4.4" dependencies: "@lezer/common": "npm:^1.0.0" - checksum: 10/c1c60beef143008b5fbc64a6713be7656b51c858b83f9199cdafb439fc4028f35544990dd7b1fc3d89a518eecb10c90f5f3bdf6804f0a4429dfddb9210275add + checksum: 10/3485153107863075fc9c813977dd1a26b43114624e440e33c9e906d5afc6ec6b236b19439196b5baf7dd2d4c34f10642aa280250758ed3d60de5cad54590b1f2 languageName: node linkType: hard @@ -10926,54 +11194,55 @@ __metadata: languageName: node linkType: hard -"@microsoft/api-documenter@npm:^7.25.7": - version: 7.25.14 - resolution: "@microsoft/api-documenter@npm:7.25.14" +"@microsoft/api-documenter@npm:^7.28.1": + version: 7.28.2 + resolution: "@microsoft/api-documenter@npm:7.28.2" dependencies: - "@microsoft/api-extractor-model": "npm:7.29.8" - "@microsoft/tsdoc": "npm:~0.15.0" - "@rushstack/node-core-library": "npm:5.9.0" - "@rushstack/terminal": "npm:0.14.2" - "@rushstack/ts-command-line": "npm:4.22.8" - js-yaml: "npm:~3.13.1" + "@microsoft/api-extractor-model": "npm:7.32.2" + "@microsoft/tsdoc": "npm:~0.16.0" + "@rushstack/node-core-library": "npm:5.19.1" + "@rushstack/terminal": "npm:0.19.5" + "@rushstack/ts-command-line": "npm:5.1.5" + js-yaml: "npm:~4.1.0" resolve: "npm:~1.22.1" bin: api-documenter: bin/api-documenter - checksum: 10/7ab46b4e8f1d84220d599339e87a518fcd95cc87fa402e39667dd2fe73a3b6f8814f22bb7f374ccb7b0a87982a6a77b96f4061c4d338ef7eee3112dd7a89700a + checksum: 10/32d40e048894d1b96dc8a8855afdd55fdd0e3c599d624c73daf616c50c965dfd0e174d380222ec9de5a5bab105029944d82c50c0b1488920233876d7077a148a languageName: node linkType: hard -"@microsoft/api-extractor-model@npm:7.29.8": - version: 7.29.8 - resolution: "@microsoft/api-extractor-model@npm:7.29.8" +"@microsoft/api-extractor-model@npm:7.32.2": + version: 7.32.2 + resolution: "@microsoft/api-extractor-model@npm:7.32.2" dependencies: - "@microsoft/tsdoc": "npm:~0.15.0" - "@microsoft/tsdoc-config": "npm:~0.17.0" - "@rushstack/node-core-library": "npm:5.9.0" - checksum: 10/06932e61f0a1979dbacc716e143f9d34d856338504cd8d016d98a3d3c83d60e7c25bfdc64d011c6ddf94de2351b52872e8722d970fce44c49bd630aeca32c987 + "@microsoft/tsdoc": "npm:~0.16.0" + "@microsoft/tsdoc-config": "npm:~0.18.0" + "@rushstack/node-core-library": "npm:5.19.1" + checksum: 10/89760055c7d3074cd903e3694c411eafa8704f048781bb2db0f0ba6e6a9a1bb12a722419ddd99141562cc8d13c98e2deee6eba118853eed630918705b223107b languageName: node linkType: hard -"@microsoft/api-extractor@npm:^7.47.2": - version: 7.47.9 - resolution: "@microsoft/api-extractor@npm:7.47.9" +"@microsoft/api-extractor@npm:^7.55.1": + version: 7.55.2 + resolution: "@microsoft/api-extractor@npm:7.55.2" dependencies: - "@microsoft/api-extractor-model": "npm:7.29.8" - "@microsoft/tsdoc": "npm:~0.15.0" - "@microsoft/tsdoc-config": "npm:~0.17.0" - "@rushstack/node-core-library": "npm:5.9.0" - "@rushstack/rig-package": "npm:0.5.3" - "@rushstack/terminal": "npm:0.14.2" - "@rushstack/ts-command-line": "npm:4.22.8" + "@microsoft/api-extractor-model": "npm:7.32.2" + "@microsoft/tsdoc": "npm:~0.16.0" + "@microsoft/tsdoc-config": "npm:~0.18.0" + "@rushstack/node-core-library": "npm:5.19.1" + "@rushstack/rig-package": "npm:0.6.0" + "@rushstack/terminal": "npm:0.19.5" + "@rushstack/ts-command-line": "npm:5.1.5" + diff: "npm:~8.0.2" lodash: "npm:~4.17.15" - minimatch: "npm:~3.0.3" + minimatch: "npm:10.0.3" resolve: "npm:~1.22.1" semver: "npm:~7.5.4" source-map: "npm:~0.6.1" - typescript: "npm:5.4.2" + typescript: "npm:5.8.2" bin: api-extractor: bin/api-extractor - checksum: 10/1814708284ed95a45969060f794b24b21a5a5e17aa88ec95099a8fc052d5c435523f465f60c4128288e210a39ff056a564fb2b8f291959ef0fc3d7423759f837 + checksum: 10/56b7e9338ad18cf3dc6aaefd679b90117c9d5498dee5c621e868a5fe5002656e62d08267525eb880221d4588afcf6d680604249a9c2fb5faaba8f9c87be16b3e languageName: node linkType: hard @@ -10991,41 +11260,52 @@ __metadata: languageName: node linkType: hard -"@microsoft/tsdoc-config@npm:~0.17.0": - version: 0.17.0 - resolution: "@microsoft/tsdoc-config@npm:0.17.0" +"@microsoft/tsdoc-config@npm:~0.18.0": + version: 0.18.0 + resolution: "@microsoft/tsdoc-config@npm:0.18.0" dependencies: - "@microsoft/tsdoc": "npm:0.15.0" + "@microsoft/tsdoc": "npm:0.16.0" ajv: "npm:~8.12.0" jju: "npm:~1.4.0" resolve: "npm:~1.22.2" - checksum: 10/6e20f9b917d20e517b6752cbb46c84ccc4c8be7ce82d7424e413bd7111a2f1497714a72e61ac1a96df97d0050cb98b3a53006316eeb0cfea9bb6d7131432c7a8 + checksum: 10/0470df5326181d876faba51617d011a632b2e3b420953e521bb865715d0db2fb0b8bfb3ccbcaef267356a1a7150d2ffba40022db5930db6b15fcb348bf846a4b languageName: node linkType: hard -"@microsoft/tsdoc@npm:0.15.0, @microsoft/tsdoc@npm:~0.15.0": - version: 0.15.0 - resolution: "@microsoft/tsdoc@npm:0.15.0" - checksum: 10/fd025e5e3966248cd5477b9ddad4e9aa0dd69291f372a207f18a686b3097dcf5ecf38325caf0f4ad2697f1f39fd45b536e4ada6756008b8bcc5eccbc3201313d +"@microsoft/tsdoc@npm:0.16.0, @microsoft/tsdoc@npm:~0.16.0": + version: 0.16.0 + resolution: "@microsoft/tsdoc@npm:0.16.0" + checksum: 10/1eaad3605234dc7e44898c15d1ba3c97fb968af1117025400cba572ce268da05afc36634d1fb9e779457af3ff7f13330aee07a962510a4d9c6612c13f71ee41e languageName: node linkType: hard -"@modelcontextprotocol/sdk@npm:^1.12.3": - version: 1.13.1 - resolution: "@modelcontextprotocol/sdk@npm:1.13.1" +"@modelcontextprotocol/sdk@npm:^1.24.3": + version: 1.24.3 + resolution: "@modelcontextprotocol/sdk@npm:1.24.3" dependencies: - ajv: "npm:^6.12.6" + ajv: "npm:^8.17.1" + ajv-formats: "npm:^3.0.1" content-type: "npm:^1.0.5" cors: "npm:^2.8.5" cross-spawn: "npm:^7.0.5" eventsource: "npm:^3.0.2" + eventsource-parser: "npm:^3.0.0" express: "npm:^5.0.1" express-rate-limit: "npm:^7.5.0" + jose: "npm:^6.1.1" pkce-challenge: "npm:^5.0.0" raw-body: "npm:^3.0.0" - zod: "npm:^3.23.8" - zod-to-json-schema: "npm:^3.24.1" - checksum: 10/b516d72e1cd14c67c8a2e5cb95fcc1c03c50be989850e3e963a7ed11000acb604e65efeaad47ea93c847130536ee51859a8d34e6dbe99d408e1a24224592e57f + zod: "npm:^3.25 || ^4.0" + zod-to-json-schema: "npm:^3.25.0" + peerDependencies: + "@cfworker/json-schema": ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + "@cfworker/json-schema": + optional: true + zod: + optional: false + checksum: 10/661aea493ee06674edc0d1409d5ff6e53053da2c3eb27d28ecdd5aa212d9d6bac8c00bec1cd18c1065f2d5774afef34e3cdcd19643056f954c14f611fee8cbc4 languageName: node linkType: hard @@ -11115,10 +11395,10 @@ __metadata: languageName: node linkType: hard -"@module-federation/error-codes@npm:0.21.4": - version: 0.21.4 - resolution: "@module-federation/error-codes@npm:0.21.4" - checksum: 10/18b0ecdba0de4cd5b202f1f5e5aa76273de0e4513ba91b8e39a2a5b00ff6471f694c88d591624b0913a0c3b4c9a6112da29dc7eebbdc562e5455b591f1d70b31 +"@module-federation/error-codes@npm:0.21.6": + version: 0.21.6 + resolution: "@module-federation/error-codes@npm:0.21.6" + checksum: 10/6ded1ecab780f1f9ec46a59adb200e75cdf11580d70aa79dd75d71fbbf276615690da277ea67aa1ceb5bc88386f5708cc1d2ba5526be5c9ff02397a6123e36bf languageName: node linkType: hard @@ -11186,13 +11466,13 @@ __metadata: languageName: node linkType: hard -"@module-federation/runtime-core@npm:0.21.4": - version: 0.21.4 - resolution: "@module-federation/runtime-core@npm:0.21.4" +"@module-federation/runtime-core@npm:0.21.6": + version: 0.21.6 + resolution: "@module-federation/runtime-core@npm:0.21.6" dependencies: - "@module-federation/error-codes": "npm:0.21.4" - "@module-federation/sdk": "npm:0.21.4" - checksum: 10/b90fe4147cd3302ea6b02098ff2bed7437425609ba0eb4d7b649b3643837810239f06f532af092a53f9b47e8fae1aeef84cfd964431fa4ab2fd4a800ad796d23 + "@module-federation/error-codes": "npm:0.21.6" + "@module-federation/sdk": "npm:0.21.6" + checksum: 10/85efa2042d6f3a7cf0e4971b991472d4339d88f6f15684afb6d451f19ed934e225b2510c86b7bb4d2c5f64253ed7b0175f08c17f95bfc2b9929930a8a03fff1e languageName: node linkType: hard @@ -11206,13 +11486,13 @@ __metadata: languageName: node linkType: hard -"@module-federation/runtime-tools@npm:0.21.4": - version: 0.21.4 - resolution: "@module-federation/runtime-tools@npm:0.21.4" +"@module-federation/runtime-tools@npm:0.21.6": + version: 0.21.6 + resolution: "@module-federation/runtime-tools@npm:0.21.6" dependencies: - "@module-federation/runtime": "npm:0.21.4" - "@module-federation/webpack-bundler-runtime": "npm:0.21.4" - checksum: 10/1e453268122070e5512c1d74cb8b4efb87cd2c1b46daba1736dfee16b2e8332a779f8168dcb3f84e17eade31f965168df63dae487ccfb74b0469c32af1895675 + "@module-federation/runtime": "npm:0.21.6" + "@module-federation/webpack-bundler-runtime": "npm:0.21.6" + checksum: 10/36e7ccab948e11f310e87397a1a2185b56064e5691e553b34173686e2bc7372ec710e5ad48c026eb28c85b168765788b743aa2111513f3b57118b47636312dd1 languageName: node linkType: hard @@ -11226,14 +11506,14 @@ __metadata: languageName: node linkType: hard -"@module-federation/runtime@npm:0.21.4": - version: 0.21.4 - resolution: "@module-federation/runtime@npm:0.21.4" +"@module-federation/runtime@npm:0.21.6": + version: 0.21.6 + resolution: "@module-federation/runtime@npm:0.21.6" dependencies: - "@module-federation/error-codes": "npm:0.21.4" - "@module-federation/runtime-core": "npm:0.21.4" - "@module-federation/sdk": "npm:0.21.4" - checksum: 10/ae262bfe1643a381e571d7dff459108da3046eea04cc3dae85dce745dd294ef8a30f70098ffa602c266b8c5878c859dd2fcde787773303dac35f77fa6ed32ae4 + "@module-federation/error-codes": "npm:0.21.6" + "@module-federation/runtime-core": "npm:0.21.6" + "@module-federation/sdk": "npm:0.21.6" + checksum: 10/93fd9bb284630933cab7e4bc070d648b56272f3636038c05eec7d1e3eeb189be3ccebe5f8ecc450197ee992d2616ed282d54e673ec0acd63adee4faddf80b144 languageName: node linkType: hard @@ -11248,10 +11528,10 @@ __metadata: languageName: node linkType: hard -"@module-federation/sdk@npm:0.21.4": - version: 0.21.4 - resolution: "@module-federation/sdk@npm:0.21.4" - checksum: 10/74c9ee2a057babf4f2638f8644a6eee6bd2c76441440dcc3855fb01a0e527e88518b8cc9c2d6d8f6b28858e34e40a3a966c03bb5d42897b9ea9163985edfa159 +"@module-federation/sdk@npm:0.21.6": + version: 0.21.6 + resolution: "@module-federation/sdk@npm:0.21.6" + checksum: 10/effc4aa932e2f06742bda8f02aaec84e138f5512b50f18c38b051490020b20d3d8edf7ece853fccffc1f78a0b43dec78e69bf02150e7e2801d5ce03c3ee367b9 languageName: node linkType: hard @@ -11273,13 +11553,13 @@ __metadata: languageName: node linkType: hard -"@module-federation/webpack-bundler-runtime@npm:0.21.4": - version: 0.21.4 - resolution: "@module-federation/webpack-bundler-runtime@npm:0.21.4" +"@module-federation/webpack-bundler-runtime@npm:0.21.6": + version: 0.21.6 + resolution: "@module-federation/webpack-bundler-runtime@npm:0.21.6" dependencies: - "@module-federation/runtime": "npm:0.21.4" - "@module-federation/sdk": "npm:0.21.4" - checksum: 10/a4f2a7ca7765651023af88f38ded9580b553cd8c6a88bc9056ec4dc58656a3d438f0498750462f424bf3aeaa5a3c7b6fd8189b7c7c76d084736474455838cb55 + "@module-federation/runtime": "npm:0.21.6" + "@module-federation/sdk": "npm:0.21.6" + checksum: 10/a5ceb72ee3867acad5d7d3c654eb568068b1d5288f60ce9301bdc9f56effa5a4c26a732a2cec7176a81b87139566cd51dd8dfbc6112da05d47b870fa3ad3ba1f languageName: node linkType: hard @@ -11398,22 +11678,22 @@ __metadata: languageName: node linkType: hard -"@mui/core-downloads-tracker@npm:^5.16.14": - version: 5.16.14 - resolution: "@mui/core-downloads-tracker@npm:5.16.14" - checksum: 10/a25658362a69a89f35cdc12ded01b998b7f02df43648029f2523813fc7f259cc85f62bd1877059359d462e7c163e82308bd4cc74fa2d35651d302c5d8bbbc7f4 +"@mui/core-downloads-tracker@npm:^5.18.0": + version: 5.18.0 + resolution: "@mui/core-downloads-tracker@npm:5.18.0" + checksum: 10/065b46739d2bd84b880ad2f6a0a2062d60e3a296ce18ff380cad22ab5b2cb3de396755f322f4bea3a422ffffe1a9244536fc3c9623056ff3873c996e6664b1b9 languageName: node linkType: hard "@mui/material@npm:^5.12.2": - version: 5.16.14 - resolution: "@mui/material@npm:5.16.14" + version: 5.18.0 + resolution: "@mui/material@npm:5.18.0" dependencies: "@babel/runtime": "npm:^7.23.9" - "@mui/core-downloads-tracker": "npm:^5.16.14" - "@mui/system": "npm:^5.16.14" - "@mui/types": "npm:^7.2.15" - "@mui/utils": "npm:^5.16.14" + "@mui/core-downloads-tracker": "npm:^5.18.0" + "@mui/system": "npm:^5.18.0" + "@mui/types": "npm:~7.2.15" + "@mui/utils": "npm:^5.17.1" "@popperjs/core": "npm:^2.11.8" "@types/react-transition-group": "npm:^4.4.10" clsx: "npm:^2.1.0" @@ -11434,16 +11714,16 @@ __metadata: optional: true "@types/react": optional: true - checksum: 10/4fe36ebe4d5f65e420895d114db81c0b8a5061e39bc18cdbebf6204953dae34cdc04af9827b65eb136e5a6853f4500a736ed3d52cce4ea37057a749eca5c3fad + checksum: 10/4b72e07c76c7c4b1076db82ef42a06dfab7d73d73f0d272019b2e0b200fc25c27bb295a8672577e1094168054159bed387cf9af74fec30e98aead7d97fad0a57 languageName: node linkType: hard -"@mui/private-theming@npm:^5.16.14": - version: 5.16.14 - resolution: "@mui/private-theming@npm:5.16.14" +"@mui/private-theming@npm:^5.17.1": + version: 5.17.1 + resolution: "@mui/private-theming@npm:5.17.1" dependencies: "@babel/runtime": "npm:^7.23.9" - "@mui/utils": "npm:^5.16.14" + "@mui/utils": "npm:^5.17.1" prop-types: "npm:^15.8.1" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -11451,16 +11731,17 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10/19cb67ccb7f9702cc2c3de99861607cc9d4109c7df578d39e6cf662f9b0108a2f4a9bf59f6c23c4e5c30a269ad7964ebd7dc2342b7f469fb9abea762a4b00bbc + checksum: 10/f8b849f545e8ab29eac959f174f56702e5b72ffda85c3b0621750e294a3f64d15873ebdb792cf478564db1c3cf4b366eabcd4156897d811949f2df079b424c8c languageName: node linkType: hard -"@mui/styled-engine@npm:^5.16.14": - version: 5.16.14 - resolution: "@mui/styled-engine@npm:5.16.14" +"@mui/styled-engine@npm:^5.18.0": + version: 5.18.0 + resolution: "@mui/styled-engine@npm:5.18.0" dependencies: "@babel/runtime": "npm:^7.23.9" "@emotion/cache": "npm:^11.13.5" + "@emotion/serialize": "npm:^1.3.3" csstype: "npm:^3.1.3" prop-types: "npm:^15.8.1" peerDependencies: @@ -11472,19 +11753,19 @@ __metadata: optional: true "@emotion/styled": optional: true - checksum: 10/d1cf2c713bab684313c6993ce63e12928f88a5033a562fa039dec4d1ce33eef3b94767470979f608b3a993dcb0ed01ef5a5a2dd9c4d4fd80419d989607ba8d75 + checksum: 10/8468a82bafb6dba40b7a3add845dd49868bcbcda3e9a0226a08f74b715dcbe2360186944ef94c44b2abe85f79335a470c0634195b9e48ccf6b5439df4bc17a90 languageName: node linkType: hard "@mui/styles@npm:^5.14.18": - version: 5.16.14 - resolution: "@mui/styles@npm:5.16.14" + version: 5.18.0 + resolution: "@mui/styles@npm:5.18.0" dependencies: "@babel/runtime": "npm:^7.23.9" "@emotion/hash": "npm:^0.9.1" - "@mui/private-theming": "npm:^5.16.14" - "@mui/types": "npm:^7.2.15" - "@mui/utils": "npm:^5.16.14" + "@mui/private-theming": "npm:^5.17.1" + "@mui/types": "npm:~7.2.15" + "@mui/utils": "npm:^5.17.1" clsx: "npm:^2.1.0" csstype: "npm:^3.1.3" hoist-non-react-statics: "npm:^3.3.2" @@ -11503,19 +11784,19 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10/53036720e7cd928c32d23c771679ce155be47e4bd5595a13abe33a4e5cf1378809f9b93609ea6307479e5a02ad82c6ca71699f74fd70c7a7993471c765e2f004 + checksum: 10/b02cc02f7c8eafe0bd5b8bc1121ee0cee50bb48e876b652313aad4c61ae466c411423e8e82f603a386ecf569c537ecfa92b6c7dabd0c798d6727e935df7fa1e6 languageName: node linkType: hard -"@mui/system@npm:^5.16.14": - version: 5.16.14 - resolution: "@mui/system@npm:5.16.14" +"@mui/system@npm:^5.16.14, @mui/system@npm:^5.18.0": + version: 5.18.0 + resolution: "@mui/system@npm:5.18.0" dependencies: "@babel/runtime": "npm:^7.23.9" - "@mui/private-theming": "npm:^5.16.14" - "@mui/styled-engine": "npm:^5.16.14" - "@mui/types": "npm:^7.2.15" - "@mui/utils": "npm:^5.16.14" + "@mui/private-theming": "npm:^5.17.1" + "@mui/styled-engine": "npm:^5.18.0" + "@mui/types": "npm:~7.2.15" + "@mui/utils": "npm:^5.17.1" clsx: "npm:^2.1.0" csstype: "npm:^3.1.3" prop-types: "npm:^15.8.1" @@ -11531,28 +11812,28 @@ __metadata: optional: true "@types/react": optional: true - checksum: 10/71892070ffe1d7b626b894776c395a748d0d8fb37c11bd22f79559d889c7b83fcbb095fab74b930d2a704d3b575720b6be4675473e7a50c92bd86411f6740232 + checksum: 10/4584a4d4f62ddaecc8b047f1a3b24ecde2ea4198963b5db3c006fd8109cd16085099862dbf935fad545ee146a3c06f119e0dd9bdc987cf45f900bab611a4afe7 languageName: node linkType: hard -"@mui/types@npm:^7.2.15": - version: 7.2.19 - resolution: "@mui/types@npm:7.2.19" +"@mui/types@npm:~7.2.15": + version: 7.2.24 + resolution: "@mui/types@npm:7.2.24" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 10/a23bc280c0722527ce5e264b0dcb44271441e4016eb2285acc1f0d236cf78c73ecc3ec7abba81876c2eadf45b905b55eb26e0e824ea6afc233efce2ef5a34f7d + checksum: 10/5ed4f90ec62c7df901e58b53011bf6b377b48e13b07de9eeb15c7a6f3f759310f0682b64685c7762f660fad6edf4c8e05595313c93810fc63c54270b899b4a75 languageName: node linkType: hard -"@mui/utils@npm:^5.14.15, @mui/utils@npm:^5.16.14": - version: 5.16.14 - resolution: "@mui/utils@npm:5.16.14" +"@mui/utils@npm:^5.14.15, @mui/utils@npm:^5.17.1": + version: 5.17.1 + resolution: "@mui/utils@npm:5.17.1" dependencies: "@babel/runtime": "npm:^7.23.9" - "@mui/types": "npm:^7.2.15" + "@mui/types": "npm:~7.2.15" "@types/prop-types": "npm:^15.7.12" clsx: "npm:^2.1.1" prop-types: "npm:^15.8.1" @@ -11563,7 +11844,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10/29bb7ca0a6e9be7bc5ab5505621566ace279fd7a2da149f0937984502d349f2b78dd42f475c5e22b546b1b27d063bd8eb8e92093b0530a814169a535dc250cdc + checksum: 10/26efae9a9f84a817b016a93ab3e3c3d08533947f62b19d4a5f8cd67ebf6932b1f68c4e4ae677dc0d3397ecd1bf1cc8cb47ab83a345bcaa9b4f45c401ec9d3926 languageName: node linkType: hard @@ -11585,6 +11866,28 @@ __metadata: languageName: node linkType: hard +"@napi-rs/wasm-runtime@npm:^0.2.11": + version: 0.2.12 + resolution: "@napi-rs/wasm-runtime@npm:0.2.12" + dependencies: + "@emnapi/core": "npm:^1.4.3" + "@emnapi/runtime": "npm:^1.4.3" + "@tybys/wasm-util": "npm:^0.10.0" + checksum: 10/5fd518182427980c28bc724adf06c5f32f9a8915763ef560b5f7d73607d30cd15ac86d0cbd2eb80d4cfab23fc80d0876d89ca36a9daadcb864bc00917c94187c + languageName: node + linkType: hard + +"@napi-rs/wasm-runtime@npm:^1.1.0": + version: 1.1.0 + resolution: "@napi-rs/wasm-runtime@npm:1.1.0" + dependencies: + "@emnapi/core": "npm:^1.7.1" + "@emnapi/runtime": "npm:^1.7.1" + "@tybys/wasm-util": "npm:^0.10.1" + checksum: 10/87c7ab4685527aa4820320020e2af5879b99d88e94b42cdc3690646722536f14656667392975a9576bf411a3804a464949fadbc343a646a2c2a8b2f10d921f6c + languageName: node + linkType: hard + "@nestjs/axios@npm:4.0.1": version: 4.0.1 resolution: "@nestjs/axios@npm:4.0.1" @@ -11664,16 +11967,6 @@ __metadata: languageName: node linkType: hard -"@nodelib/fs.scandir@npm:4.0.1": - version: 4.0.1 - resolution: "@nodelib/fs.scandir@npm:4.0.1" - dependencies: - "@nodelib/fs.stat": "npm:4.0.0" - run-parallel: "npm:^1.2.0" - checksum: 10/44b2b2b34e48ca88ee004413f5033db31cd6d5ecf8c7bbef0e33b6672d603f3e23b57d5fbb1bd5f83f8992df58381be6600006d92a903f085e698a37bdfe3c89 - languageName: node - linkType: hard - "@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": version: 2.0.5 resolution: "@nodelib/fs.stat@npm:2.0.5" @@ -11681,23 +11974,6 @@ __metadata: languageName: node linkType: hard -"@nodelib/fs.stat@npm:4.0.0": - version: 4.0.0 - resolution: "@nodelib/fs.stat@npm:4.0.0" - checksum: 10/1f87199fdab938d2ed6f5e10debc006f7965081e2cd147ed3d2333049a030cad1949bd76556a5f5364f062c3e1edcc3d0981189b065336fc92c503ead463f4e1 - languageName: node - linkType: hard - -"@nodelib/fs.walk@npm:3.0.1": - version: 3.0.1 - resolution: "@nodelib/fs.walk@npm:3.0.1" - dependencies: - "@nodelib/fs.scandir": "npm:4.0.1" - fastq: "npm:^1.15.0" - checksum: 10/7b76a0139dec52e3f2a3a0bb4f13dbf72a6b79d8076ec4b5deea9e75bd1b79d7abda53776f93b5aefda9a5e40f0e31f49f6e35bf5460a402f0aee7bcf3b26d85 - languageName: node - linkType: hard - "@nodelib/fs.walk@npm:^1.2.3, @nodelib/fs.walk@npm:^1.2.8": version: 1.2.8 resolution: "@nodelib/fs.walk@npm:1.2.8" @@ -12671,8 +12947,8 @@ __metadata: linkType: hard "@openapitools/openapi-generator-cli@npm:^2.4.26, @openapitools/openapi-generator-cli@npm:^2.7.0": - version: 2.25.1 - resolution: "@openapitools/openapi-generator-cli@npm:2.25.1" + version: 2.25.2 + resolution: "@openapitools/openapi-generator-cli@npm:2.25.2" dependencies: "@nestjs/axios": "npm:4.0.1" "@nestjs/common": "npm:11.1.9" @@ -12685,7 +12961,7 @@ __metadata: concurrently: "npm:9.2.1" console.table: "npm:0.10.0" fs-extra: "npm:11.3.2" - glob: "npm:12.0.0" + glob: "npm:13.0.0" inquirer: "npm:8.2.7" proxy-agent: "npm:6.5.0" reflect-metadata: "npm:0.2.2" @@ -12693,7 +12969,7 @@ __metadata: tslib: "npm:2.8.1" bin: openapi-generator-cli: main.js - checksum: 10/4c414eb359de26c0b10e8cbec101298cfa1cc2745a36b982b95bb970a8260c3557fa0b2aa116fa5cb36e4b2e5146b81d7a75a2e5da71f1d529038304a02b59fc + checksum: 10/8a6e2990f4816aadb8ac6f390aeb169fe2f842f8874417dd288ea4b177af9238a7af59c699953e644d17eeb6c5d712faf63ce3bf9419e429e71504e2033de7b6 languageName: node linkType: hard @@ -12711,21 +12987,12 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/api-logs@npm:0.202.0, @opentelemetry/api-logs@npm:^0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/api-logs@npm:0.202.0" +"@opentelemetry/api-logs@npm:0.208.0, @opentelemetry/api-logs@npm:^0.208.0": + version: 0.208.0 + resolution: "@opentelemetry/api-logs@npm:0.208.0" dependencies: "@opentelemetry/api": "npm:^1.3.0" - checksum: 10/22171137ad1d876a79a6f046b4adcc44a13941a07f2172948e4c8dbb6cacfe276b1fa5087f15e4acdd567f748010d531321c93ebf93a6c09586351c9048bf6c7 - languageName: node - linkType: hard - -"@opentelemetry/api-logs@npm:0.54.2": - version: 0.54.2 - resolution: "@opentelemetry/api-logs@npm:0.54.2" - dependencies: - "@opentelemetry/api": "npm:^1.3.0" - checksum: 10/97d887be03ca4a2e69574cc9160464bda00f2a167cc850656ade44b6690a75855d9334983b73827dc44c3672958bc478197f261eae11c2ac68a6df9260c9c3df + checksum: 10/ae339416a244e90b1718af1ed5430348188be60871f3799c847bab409bba1513337cac7da40b4883bf7f280680754319b44a9dc95fa2879d15c0413c9955b145 languageName: node linkType: hard @@ -12736,102 +13003,83 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/auto-instrumentations-node@npm:^0.61.0": - version: 0.61.0 - resolution: "@opentelemetry/auto-instrumentations-node@npm:0.61.0" +"@opentelemetry/auto-instrumentations-node@npm:^0.67.0": + version: 0.67.2 + resolution: "@opentelemetry/auto-instrumentations-node@npm:0.67.2" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/instrumentation-amqplib": "npm:^0.49.0" - "@opentelemetry/instrumentation-aws-lambda": "npm:^0.53.1" - "@opentelemetry/instrumentation-aws-sdk": "npm:^0.55.0" - "@opentelemetry/instrumentation-bunyan": "npm:^0.48.0" - "@opentelemetry/instrumentation-cassandra-driver": "npm:^0.48.0" - "@opentelemetry/instrumentation-connect": "npm:^0.46.0" - "@opentelemetry/instrumentation-cucumber": "npm:^0.17.1" - "@opentelemetry/instrumentation-dataloader": "npm:^0.20.0" - "@opentelemetry/instrumentation-dns": "npm:^0.46.0" - "@opentelemetry/instrumentation-express": "npm:^0.51.1" - "@opentelemetry/instrumentation-fastify": "npm:^0.47.1" - "@opentelemetry/instrumentation-fs": "npm:^0.22.0" - "@opentelemetry/instrumentation-generic-pool": "npm:^0.46.1" - "@opentelemetry/instrumentation-graphql": "npm:^0.50.0" - "@opentelemetry/instrumentation-grpc": "npm:^0.202.0" - "@opentelemetry/instrumentation-hapi": "npm:^0.49.0" - "@opentelemetry/instrumentation-http": "npm:^0.202.0" - "@opentelemetry/instrumentation-ioredis": "npm:^0.50.1" - "@opentelemetry/instrumentation-kafkajs": "npm:^0.11.0" - "@opentelemetry/instrumentation-knex": "npm:^0.47.0" - "@opentelemetry/instrumentation-koa": "npm:^0.50.2" - "@opentelemetry/instrumentation-lru-memoizer": "npm:^0.47.0" - "@opentelemetry/instrumentation-memcached": "npm:^0.46.0" - "@opentelemetry/instrumentation-mongodb": "npm:^0.55.1" - "@opentelemetry/instrumentation-mongoose": "npm:^0.49.0" - "@opentelemetry/instrumentation-mysql": "npm:^0.48.1" - "@opentelemetry/instrumentation-mysql2": "npm:^0.48.1" - "@opentelemetry/instrumentation-nestjs-core": "npm:^0.48.1" - "@opentelemetry/instrumentation-net": "npm:^0.46.1" - "@opentelemetry/instrumentation-oracledb": "npm:^0.28.0" - "@opentelemetry/instrumentation-pg": "npm:^0.54.1" - "@opentelemetry/instrumentation-pino": "npm:^0.49.1" - "@opentelemetry/instrumentation-redis": "npm:^0.50.0" - "@opentelemetry/instrumentation-restify": "npm:^0.48.2" - "@opentelemetry/instrumentation-router": "npm:^0.47.0" - "@opentelemetry/instrumentation-runtime-node": "npm:^0.16.0" - "@opentelemetry/instrumentation-socket.io": "npm:^0.49.0" - "@opentelemetry/instrumentation-tedious": "npm:^0.21.1" - "@opentelemetry/instrumentation-undici": "npm:^0.13.2" - "@opentelemetry/instrumentation-winston": "npm:^0.47.0" - "@opentelemetry/resource-detector-alibaba-cloud": "npm:^0.31.2" - "@opentelemetry/resource-detector-aws": "npm:^2.2.0" - "@opentelemetry/resource-detector-azure": "npm:^0.9.0" - "@opentelemetry/resource-detector-container": "npm:^0.7.2" - "@opentelemetry/resource-detector-gcp": "npm:^0.36.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/instrumentation-amqplib": "npm:^0.55.0" + "@opentelemetry/instrumentation-aws-lambda": "npm:^0.61.0" + "@opentelemetry/instrumentation-aws-sdk": "npm:^0.64.0" + "@opentelemetry/instrumentation-bunyan": "npm:^0.54.0" + "@opentelemetry/instrumentation-cassandra-driver": "npm:^0.54.0" + "@opentelemetry/instrumentation-connect": "npm:^0.52.0" + "@opentelemetry/instrumentation-cucumber": "npm:^0.24.0" + "@opentelemetry/instrumentation-dataloader": "npm:^0.26.0" + "@opentelemetry/instrumentation-dns": "npm:^0.52.0" + "@opentelemetry/instrumentation-express": "npm:^0.57.0" + "@opentelemetry/instrumentation-fastify": "npm:^0.53.0" + "@opentelemetry/instrumentation-fs": "npm:^0.28.0" + "@opentelemetry/instrumentation-generic-pool": "npm:^0.52.0" + "@opentelemetry/instrumentation-graphql": "npm:^0.56.0" + "@opentelemetry/instrumentation-grpc": "npm:^0.208.0" + "@opentelemetry/instrumentation-hapi": "npm:^0.55.0" + "@opentelemetry/instrumentation-http": "npm:^0.208.0" + "@opentelemetry/instrumentation-ioredis": "npm:^0.56.0" + "@opentelemetry/instrumentation-kafkajs": "npm:^0.18.0" + "@opentelemetry/instrumentation-knex": "npm:^0.53.1" + "@opentelemetry/instrumentation-koa": "npm:^0.57.0" + "@opentelemetry/instrumentation-lru-memoizer": "npm:^0.53.0" + "@opentelemetry/instrumentation-memcached": "npm:^0.52.0" + "@opentelemetry/instrumentation-mongodb": "npm:^0.61.0" + "@opentelemetry/instrumentation-mongoose": "npm:^0.55.0" + "@opentelemetry/instrumentation-mysql": "npm:^0.54.0" + "@opentelemetry/instrumentation-mysql2": "npm:^0.55.0" + "@opentelemetry/instrumentation-nestjs-core": "npm:^0.55.0" + "@opentelemetry/instrumentation-net": "npm:^0.52.0" + "@opentelemetry/instrumentation-openai": "npm:^0.7.0" + "@opentelemetry/instrumentation-oracledb": "npm:^0.34.0" + "@opentelemetry/instrumentation-pg": "npm:^0.61.1" + "@opentelemetry/instrumentation-pino": "npm:^0.55.0" + "@opentelemetry/instrumentation-redis": "npm:^0.57.1" + "@opentelemetry/instrumentation-restify": "npm:^0.54.0" + "@opentelemetry/instrumentation-router": "npm:^0.53.0" + "@opentelemetry/instrumentation-runtime-node": "npm:^0.22.0" + "@opentelemetry/instrumentation-socket.io": "npm:^0.55.0" + "@opentelemetry/instrumentation-tedious": "npm:^0.27.0" + "@opentelemetry/instrumentation-undici": "npm:^0.19.0" + "@opentelemetry/instrumentation-winston": "npm:^0.53.0" + "@opentelemetry/resource-detector-alibaba-cloud": "npm:^0.31.11" + "@opentelemetry/resource-detector-aws": "npm:^2.8.0" + "@opentelemetry/resource-detector-azure": "npm:^0.16.0" + "@opentelemetry/resource-detector-container": "npm:^0.7.11" + "@opentelemetry/resource-detector-gcp": "npm:^0.43.0" "@opentelemetry/resources": "npm:^2.0.0" - "@opentelemetry/sdk-node": "npm:^0.202.0" + "@opentelemetry/sdk-node": "npm:^0.208.0" peerDependencies: "@opentelemetry/api": ^1.4.1 "@opentelemetry/core": ^2.0.0 - checksum: 10/845728e050aa40f501ca3c67ad10ef64859413cb3af6926c6e64dbec206dc4ee7c8cff4d93df95345cebf8970427a03665032f97941a6a1153699f0fbbac1a6f + checksum: 10/b45d91919ccd0bf4ac9a83bb82a9620fa323cf66f5ab95de56a627ae1b05330791104a8c0d317fd8a1a29e956a6b663e6908396476e7ad37d59811136976b167 languageName: node linkType: hard -"@opentelemetry/context-async-hooks@npm:1.27.0": - version: 1.27.0 - resolution: "@opentelemetry/context-async-hooks@npm:1.27.0" +"@opentelemetry/context-async-hooks@npm:2.2.0": + version: 2.2.0 + resolution: "@opentelemetry/context-async-hooks@npm:2.2.0" peerDependencies: "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/a72fdf5754f6e6d829b81031afe1a8e48a66bb02b13014e05c3fbb9c31fc736f7d303b0bb3491d200ce951582fe04a2d1c6246359683e8fe1b544929d5fd16c5 + checksum: 10/00ee1a35ce9f96632955830d54672025db6d4dc77d440c183cd9751efd5bdfeb8a078094d3d1caaf8b3c250def098990a98bfd8254b1ec04b759f1e3f2fc224e languageName: node linkType: hard -"@opentelemetry/context-async-hooks@npm:2.0.1": - version: 2.0.1 - resolution: "@opentelemetry/context-async-hooks@npm:2.0.1" - peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/198dacdce36377f6ded7062eb9fc77f86c9fcc8c86dd395e9cb276e14a01c7906ea2f0271999cae21bf251dbec44641286b8bd34a39c67c88c08ed925a986f4b - languageName: node - linkType: hard - -"@opentelemetry/core@npm:1.27.0": - version: 1.27.0 - resolution: "@opentelemetry/core@npm:1.27.0" - dependencies: - "@opentelemetry/semantic-conventions": "npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/2e64f35f7f8a53c035eb7e2335c73a6bca0f12a0d45cd8171646492d5efb73f82fb29aae77f34b2d6e93498b38172dee8e5cf769727c44ac08be0d5b21da7512 - languageName: node - linkType: hard - -"@opentelemetry/core@npm:2.0.1, @opentelemetry/core@npm:^2.0.0": - version: 2.0.1 - resolution: "@opentelemetry/core@npm:2.0.1" +"@opentelemetry/core@npm:2.2.0, @opentelemetry/core@npm:^2.0.0": + version: 2.2.0 + resolution: "@opentelemetry/core@npm:2.2.0" dependencies: "@opentelemetry/semantic-conventions": "npm:^1.29.0" peerDependencies: "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/dd891afd427067a9e6c610c36ab5638b0b9e5303ccca7c75ad744f5db53c6162a4b5d9cd2f5a77cdc3e4bda2eae850a4e29983ea244c929b7b872b7e086fc61c + checksum: 10/f25193ba8b1fadb7bd8ed0d86ac39dd0f3fd3eec47c2fb2745bd22442b2d5e3ca88e5cab6d97111349d3182bf8e4356f8b7c7213ebea8f7719de944ce13a19cb languageName: node linkType: hard @@ -12846,1230 +13094,922 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/exporter-logs-otlp-grpc@npm:0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/exporter-logs-otlp-grpc@npm:0.202.0" +"@opentelemetry/exporter-logs-otlp-grpc@npm:0.208.0": + version: 0.208.0 + resolution: "@opentelemetry/exporter-logs-otlp-grpc@npm:0.208.0" dependencies: "@grpc/grpc-js": "npm:^1.7.1" - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/otlp-exporter-base": "npm:0.202.0" - "@opentelemetry/otlp-grpc-exporter-base": "npm:0.202.0" - "@opentelemetry/otlp-transformer": "npm:0.202.0" - "@opentelemetry/sdk-logs": "npm:0.202.0" + "@opentelemetry/core": "npm:2.2.0" + "@opentelemetry/otlp-exporter-base": "npm:0.208.0" + "@opentelemetry/otlp-grpc-exporter-base": "npm:0.208.0" + "@opentelemetry/otlp-transformer": "npm:0.208.0" + "@opentelemetry/sdk-logs": "npm:0.208.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/b293a3778b6d5b1fe07b9203fe83d0dde3ea57e48efec8d4ccfc27d007fdb5cdafe7446ff10ea3eca69eb2fba8bdb97546a7dfcfc21b73d303e94e15f917b253 + checksum: 10/ed1d00184b8d03aed376a39363c1561eb4d26155147b53dda36f9c9c29e1e144a783c8d5dbeb1f91a157243777d7261b5e24c38202c5cec6d85dc6c45643d96c languageName: node linkType: hard -"@opentelemetry/exporter-logs-otlp-grpc@npm:0.54.2": - version: 0.54.2 - resolution: "@opentelemetry/exporter-logs-otlp-grpc@npm:0.54.2" +"@opentelemetry/exporter-logs-otlp-http@npm:0.208.0": + version: 0.208.0 + resolution: "@opentelemetry/exporter-logs-otlp-http@npm:0.208.0" + dependencies: + "@opentelemetry/api-logs": "npm:0.208.0" + "@opentelemetry/core": "npm:2.2.0" + "@opentelemetry/otlp-exporter-base": "npm:0.208.0" + "@opentelemetry/otlp-transformer": "npm:0.208.0" + "@opentelemetry/sdk-logs": "npm:0.208.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/b200e95bc71bc3c6591ef1f38a37ae27de5d2164a0e0df2b319369f026aaa6df080ed5d5fab02906b35f4c6e02a829ba0b09e041b506ade4b5c3240caafc420d + languageName: node + linkType: hard + +"@opentelemetry/exporter-logs-otlp-proto@npm:0.208.0": + version: 0.208.0 + resolution: "@opentelemetry/exporter-logs-otlp-proto@npm:0.208.0" + dependencies: + "@opentelemetry/api-logs": "npm:0.208.0" + "@opentelemetry/core": "npm:2.2.0" + "@opentelemetry/otlp-exporter-base": "npm:0.208.0" + "@opentelemetry/otlp-transformer": "npm:0.208.0" + "@opentelemetry/resources": "npm:2.2.0" + "@opentelemetry/sdk-logs": "npm:0.208.0" + "@opentelemetry/sdk-trace-base": "npm:2.2.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/71e11a3cb0fdca976ce644443846e4aaaad915848c99a212369e6b2b0cba664bf0cfbccc270f1d4210c6fc925b08a2dfb58f263d219fac39c5243993dcba7aa1 + languageName: node + linkType: hard + +"@opentelemetry/exporter-metrics-otlp-grpc@npm:0.208.0": + version: 0.208.0 + resolution: "@opentelemetry/exporter-metrics-otlp-grpc@npm:0.208.0" dependencies: "@grpc/grpc-js": "npm:^1.7.1" - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/otlp-grpc-exporter-base": "npm:0.54.2" - "@opentelemetry/otlp-transformer": "npm:0.54.2" - "@opentelemetry/sdk-logs": "npm:0.54.2" + "@opentelemetry/core": "npm:2.2.0" + "@opentelemetry/exporter-metrics-otlp-http": "npm:0.208.0" + "@opentelemetry/otlp-exporter-base": "npm:0.208.0" + "@opentelemetry/otlp-grpc-exporter-base": "npm:0.208.0" + "@opentelemetry/otlp-transformer": "npm:0.208.0" + "@opentelemetry/resources": "npm:2.2.0" + "@opentelemetry/sdk-metrics": "npm:2.2.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/eacb27329ad5a8154f0f075e20de146cd18b673dfc3db88596678f95afa8939bc853e98e397a817dcf4e5cc024ff7e53320041576b2ce7c0dffd1067e7229b9e + checksum: 10/495ac28c27781750ae19d1e6405e5dd9ff277547e4ab1015e4b3fd2a1d8c26b0977a62dd20ac4e263a980bbace6a2fbe8fed91ae289dddfed6a5a55db0df861e languageName: node linkType: hard -"@opentelemetry/exporter-logs-otlp-http@npm:0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/exporter-logs-otlp-http@npm:0.202.0" +"@opentelemetry/exporter-metrics-otlp-http@npm:0.208.0": + version: 0.208.0 + resolution: "@opentelemetry/exporter-metrics-otlp-http@npm:0.208.0" dependencies: - "@opentelemetry/api-logs": "npm:0.202.0" - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/otlp-exporter-base": "npm:0.202.0" - "@opentelemetry/otlp-transformer": "npm:0.202.0" - "@opentelemetry/sdk-logs": "npm:0.202.0" + "@opentelemetry/core": "npm:2.2.0" + "@opentelemetry/otlp-exporter-base": "npm:0.208.0" + "@opentelemetry/otlp-transformer": "npm:0.208.0" + "@opentelemetry/resources": "npm:2.2.0" + "@opentelemetry/sdk-metrics": "npm:2.2.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/850be5f7a2b0468887357845ffb7f8ca53dfedcd8fc79fbd4d8825cabbdc9a27be107e7aecb6b21ba544cead796e13f116396fcc66ba58954e47ac8fc1eaf270 + checksum: 10/ea4e14481e3a7cd08574f811bf62527305a735972818cc4bb4b2232d19c65732200266de16ae0a59dafd8aa27b7acff77af6e3e300df457841b7e2f5f97ee43b languageName: node linkType: hard -"@opentelemetry/exporter-logs-otlp-http@npm:0.54.2": - version: 0.54.2 - resolution: "@opentelemetry/exporter-logs-otlp-http@npm:0.54.2" +"@opentelemetry/exporter-metrics-otlp-proto@npm:0.208.0": + version: 0.208.0 + resolution: "@opentelemetry/exporter-metrics-otlp-proto@npm:0.208.0" dependencies: - "@opentelemetry/api-logs": "npm:0.54.2" - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/otlp-exporter-base": "npm:0.54.2" - "@opentelemetry/otlp-transformer": "npm:0.54.2" - "@opentelemetry/sdk-logs": "npm:0.54.2" + "@opentelemetry/core": "npm:2.2.0" + "@opentelemetry/exporter-metrics-otlp-http": "npm:0.208.0" + "@opentelemetry/otlp-exporter-base": "npm:0.208.0" + "@opentelemetry/otlp-transformer": "npm:0.208.0" + "@opentelemetry/resources": "npm:2.2.0" + "@opentelemetry/sdk-metrics": "npm:2.2.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/88e99302310cef38002c5cc2c682197b8bc5bee5a1dd139d7a9fb5b2645464071e86a4f55607dbf1afae5a4d91c41ddfc982e7230c7ed6e4667e5709b7a9e3c0 + checksum: 10/06d96d582294e3f792508f49a2138b926bde85545057260a97bb7aaed7b935f424e41c25c9b94f5781a1902bc77bcf9c0308d4a8d9e9e1539eb89567adb8b28f languageName: node linkType: hard -"@opentelemetry/exporter-logs-otlp-proto@npm:0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/exporter-logs-otlp-proto@npm:0.202.0" +"@opentelemetry/exporter-prometheus@npm:0.208.0, @opentelemetry/exporter-prometheus@npm:^0.208.0": + version: 0.208.0 + resolution: "@opentelemetry/exporter-prometheus@npm:0.208.0" dependencies: - "@opentelemetry/api-logs": "npm:0.202.0" - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/otlp-exporter-base": "npm:0.202.0" - "@opentelemetry/otlp-transformer": "npm:0.202.0" - "@opentelemetry/resources": "npm:2.0.1" - "@opentelemetry/sdk-logs": "npm:0.202.0" - "@opentelemetry/sdk-trace-base": "npm:2.0.1" + "@opentelemetry/core": "npm:2.2.0" + "@opentelemetry/resources": "npm:2.2.0" + "@opentelemetry/sdk-metrics": "npm:2.2.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/b69e19a035ae7d2d3f99f5ef40760e9f874685de9679b09a143ae9c120a248d9b5995eeec869ebeed6df1a4ebb4e312b5a569f4189410ad02e6ec4438f8fd63f + checksum: 10/3aae6be7f694e8e7add17c76f7c37660003026fc1feb3f5e2d4b6ff171426a96fd595384b7ecca19a5b2a58d887a3fc87906d4524dd8fb8424a1022d595dabb6 languageName: node linkType: hard -"@opentelemetry/exporter-logs-otlp-proto@npm:0.54.2": - version: 0.54.2 - resolution: "@opentelemetry/exporter-logs-otlp-proto@npm:0.54.2" - dependencies: - "@opentelemetry/api-logs": "npm:0.54.2" - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/otlp-exporter-base": "npm:0.54.2" - "@opentelemetry/otlp-transformer": "npm:0.54.2" - "@opentelemetry/resources": "npm:1.27.0" - "@opentelemetry/sdk-logs": "npm:0.54.2" - "@opentelemetry/sdk-trace-base": "npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/2b2d6bfe1d0799d0f96fac5a15712c8dd3ef132380ee1856d03672c54802fa9f7664a0897310de708cd42942f916d0fdf20916cf4c5459ef7148db836cd5b1f2 - languageName: node - linkType: hard - -"@opentelemetry/exporter-metrics-otlp-grpc@npm:0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/exporter-metrics-otlp-grpc@npm:0.202.0" +"@opentelemetry/exporter-trace-otlp-grpc@npm:0.208.0": + version: 0.208.0 + resolution: "@opentelemetry/exporter-trace-otlp-grpc@npm:0.208.0" dependencies: "@grpc/grpc-js": "npm:^1.7.1" - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/exporter-metrics-otlp-http": "npm:0.202.0" - "@opentelemetry/otlp-exporter-base": "npm:0.202.0" - "@opentelemetry/otlp-grpc-exporter-base": "npm:0.202.0" - "@opentelemetry/otlp-transformer": "npm:0.202.0" - "@opentelemetry/resources": "npm:2.0.1" - "@opentelemetry/sdk-metrics": "npm:2.0.1" + "@opentelemetry/core": "npm:2.2.0" + "@opentelemetry/otlp-exporter-base": "npm:0.208.0" + "@opentelemetry/otlp-grpc-exporter-base": "npm:0.208.0" + "@opentelemetry/otlp-transformer": "npm:0.208.0" + "@opentelemetry/resources": "npm:2.2.0" + "@opentelemetry/sdk-trace-base": "npm:2.2.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/383e6eee1016dbdfe78ed8c96fb6e8e314e0103a11353373f794637594b2b935efc5707eee33712a4ed242b12c808a09584105db1b2e2deab477d80e3d2d06ed + checksum: 10/5405fb3ecf2ab9fcb8279e13ed89eb490ea645cfd9662a064d9bbd7e97c8ff838cd52908d14e27509aa9d00dbded22a9bc707436c54220c114d29c51abddb31f languageName: node linkType: hard -"@opentelemetry/exporter-metrics-otlp-http@npm:0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/exporter-metrics-otlp-http@npm:0.202.0" +"@opentelemetry/exporter-trace-otlp-http@npm:0.208.0": + version: 0.208.0 + resolution: "@opentelemetry/exporter-trace-otlp-http@npm:0.208.0" dependencies: - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/otlp-exporter-base": "npm:0.202.0" - "@opentelemetry/otlp-transformer": "npm:0.202.0" - "@opentelemetry/resources": "npm:2.0.1" - "@opentelemetry/sdk-metrics": "npm:2.0.1" + "@opentelemetry/core": "npm:2.2.0" + "@opentelemetry/otlp-exporter-base": "npm:0.208.0" + "@opentelemetry/otlp-transformer": "npm:0.208.0" + "@opentelemetry/resources": "npm:2.2.0" + "@opentelemetry/sdk-trace-base": "npm:2.2.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/ed694b8369bb2f979f0aa3552716b267060e5d8d7853fd645e0ae776888091a08c079e9f1e88d8f7eb93d9fe31f0fbb887ebb9469177c2ec2af16c247f8faf5d + checksum: 10/69cbd8a223ddd774db97fe3b8e55063ac02e78502a7378596c14101dc98256cef3a5ef0092808ab73d42d09393e000a9eb2041c0d3c087efeb3864bb57f49907 languageName: node linkType: hard -"@opentelemetry/exporter-metrics-otlp-proto@npm:0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/exporter-metrics-otlp-proto@npm:0.202.0" +"@opentelemetry/exporter-trace-otlp-proto@npm:0.208.0": + version: 0.208.0 + resolution: "@opentelemetry/exporter-trace-otlp-proto@npm:0.208.0" dependencies: - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/exporter-metrics-otlp-http": "npm:0.202.0" - "@opentelemetry/otlp-exporter-base": "npm:0.202.0" - "@opentelemetry/otlp-transformer": "npm:0.202.0" - "@opentelemetry/resources": "npm:2.0.1" - "@opentelemetry/sdk-metrics": "npm:2.0.1" + "@opentelemetry/core": "npm:2.2.0" + "@opentelemetry/otlp-exporter-base": "npm:0.208.0" + "@opentelemetry/otlp-transformer": "npm:0.208.0" + "@opentelemetry/resources": "npm:2.2.0" + "@opentelemetry/sdk-trace-base": "npm:2.2.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/22ecc54c4a19a36d5f6ba8cc061e25fba6ff9f2bbaff9a84f8c1e7e5253b2b0dc07ac18e21884eafcb31e6693ce59912baeb19c722a07ad4da9f22e2a6f94549 + checksum: 10/af948730fd917471c220fcb9ad5c05bb3ab365e1cd8429fb69f50e855aa76d516a64307c52bb7e756fdd768c02806940d5fe060506191d231903e7db158dfb12 languageName: node linkType: hard -"@opentelemetry/exporter-prometheus@npm:0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/exporter-prometheus@npm:0.202.0" +"@opentelemetry/exporter-zipkin@npm:2.2.0": + version: 2.2.0 + resolution: "@opentelemetry/exporter-zipkin@npm:2.2.0" dependencies: - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/resources": "npm:2.0.1" - "@opentelemetry/sdk-metrics": "npm:2.0.1" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/e6cf4dfdc3bd6ccd402a00e0cb1bd2fa5cc4c813a86b23d27bf851e38a22788dac234c10d643a1506acf10415b1cba9bd68da7554358ba32a0ad8b978bc2d467 - languageName: node - linkType: hard - -"@opentelemetry/exporter-prometheus@npm:^0.54.0": - version: 0.54.2 - resolution: "@opentelemetry/exporter-prometheus@npm:0.54.2" - dependencies: - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/resources": "npm:1.27.0" - "@opentelemetry/sdk-metrics": "npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/1e2e549ad0b0659c92e5ba7b4fff58754f0199f01d968e5031f4bb6b6ef83c4791f7c74527408ed4661e68908ee728c27ab041c03d62bf2ed16398a7e646afb0 - languageName: node - linkType: hard - -"@opentelemetry/exporter-trace-otlp-grpc@npm:0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/exporter-trace-otlp-grpc@npm:0.202.0" - dependencies: - "@grpc/grpc-js": "npm:^1.7.1" - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/otlp-exporter-base": "npm:0.202.0" - "@opentelemetry/otlp-grpc-exporter-base": "npm:0.202.0" - "@opentelemetry/otlp-transformer": "npm:0.202.0" - "@opentelemetry/resources": "npm:2.0.1" - "@opentelemetry/sdk-trace-base": "npm:2.0.1" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/28cd0d2a0d3ef08cd7830b044690c5e6d86eb36f0d1298d9cd921f89c382cfbd3f80272c9fb8c12ece7eb417a48f852f6b3c299c21b0eba5d340d244cd41e6e2 - languageName: node - linkType: hard - -"@opentelemetry/exporter-trace-otlp-grpc@npm:0.54.2": - version: 0.54.2 - resolution: "@opentelemetry/exporter-trace-otlp-grpc@npm:0.54.2" - dependencies: - "@grpc/grpc-js": "npm:^1.7.1" - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/otlp-grpc-exporter-base": "npm:0.54.2" - "@opentelemetry/otlp-transformer": "npm:0.54.2" - "@opentelemetry/resources": "npm:1.27.0" - "@opentelemetry/sdk-trace-base": "npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/4fd0f50f1faca2ddc8167420517b0f03760fe113e3240fcfc1324d2e0779065b92f2f08775cae020e7a51bf80d202328e44159e89830b160f41e7c1b507debaf - languageName: node - linkType: hard - -"@opentelemetry/exporter-trace-otlp-http@npm:0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/exporter-trace-otlp-http@npm:0.202.0" - dependencies: - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/otlp-exporter-base": "npm:0.202.0" - "@opentelemetry/otlp-transformer": "npm:0.202.0" - "@opentelemetry/resources": "npm:2.0.1" - "@opentelemetry/sdk-trace-base": "npm:2.0.1" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/f9e60e51b5dcca3d4d32f2091f04faa2bcaf677b9f8a88990c8cdc7c6345e4244e46c4e8df1c336fc6f741aea092407032ade1236089eb729196e6fcdc055ad0 - languageName: node - linkType: hard - -"@opentelemetry/exporter-trace-otlp-http@npm:0.54.2": - version: 0.54.2 - resolution: "@opentelemetry/exporter-trace-otlp-http@npm:0.54.2" - dependencies: - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/otlp-exporter-base": "npm:0.54.2" - "@opentelemetry/otlp-transformer": "npm:0.54.2" - "@opentelemetry/resources": "npm:1.27.0" - "@opentelemetry/sdk-trace-base": "npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/0826980f3509795b48b694bc5c1035e1800ddc0697e29662dd02a6ec09ebe40a57f3c977482cf91b90a6eb5ed99b6a9b1401a144c868c6f4196c6c50da84600a - languageName: node - linkType: hard - -"@opentelemetry/exporter-trace-otlp-proto@npm:0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/exporter-trace-otlp-proto@npm:0.202.0" - dependencies: - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/otlp-exporter-base": "npm:0.202.0" - "@opentelemetry/otlp-transformer": "npm:0.202.0" - "@opentelemetry/resources": "npm:2.0.1" - "@opentelemetry/sdk-trace-base": "npm:2.0.1" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/884d1d097f3defee57dbb64281815a062603f64ce81e2f4460477925f4fb23c1d42aac2bb9d3b188e00cacaa9d231ef11e2e0f36e4b26863d009e795953191d8 - languageName: node - linkType: hard - -"@opentelemetry/exporter-trace-otlp-proto@npm:0.54.2": - version: 0.54.2 - resolution: "@opentelemetry/exporter-trace-otlp-proto@npm:0.54.2" - dependencies: - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/otlp-exporter-base": "npm:0.54.2" - "@opentelemetry/otlp-transformer": "npm:0.54.2" - "@opentelemetry/resources": "npm:1.27.0" - "@opentelemetry/sdk-trace-base": "npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/3f128e60833c6cc896209ee787234be930ea326d9006973f1f0bc61903715c4ae8d60e46ab4b66cd9baa008c8682b9f8ddef6b99ea8b2929aa4745075a593630 - languageName: node - linkType: hard - -"@opentelemetry/exporter-zipkin@npm:1.27.0": - version: 1.27.0 - resolution: "@opentelemetry/exporter-zipkin@npm:1.27.0" - dependencies: - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/resources": "npm:1.27.0" - "@opentelemetry/sdk-trace-base": "npm:1.27.0" - "@opentelemetry/semantic-conventions": "npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.0.0 - checksum: 10/c1cf75eae527b9159b9e52b9b073d3a62a8e407812832a10bb1c110aad2764692239cb99e46f56b2d4d72fe548494dd829bbf2c25611827fc1b031d8af69e8ef - languageName: node - linkType: hard - -"@opentelemetry/exporter-zipkin@npm:2.0.1": - version: 2.0.1 - resolution: "@opentelemetry/exporter-zipkin@npm:2.0.1" - dependencies: - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/resources": "npm:2.0.1" - "@opentelemetry/sdk-trace-base": "npm:2.0.1" + "@opentelemetry/core": "npm:2.2.0" + "@opentelemetry/resources": "npm:2.2.0" + "@opentelemetry/sdk-trace-base": "npm:2.2.0" "@opentelemetry/semantic-conventions": "npm:^1.29.0" peerDependencies: "@opentelemetry/api": ^1.0.0 - checksum: 10/67fa9c4e33276218fd8a4c6fb04bab55aaf35383e396056034ab1f826f3d00c672b91ec890a9859df13c9f2890d3dc3d9f1a2b2904f0855272fc0cb48157e874 + checksum: 10/5e7b1464810ab2de77cee437dfc4436ade5c5391a9b80cdd5c1495f3aa32cf8e4cf458cae327dc477260876a8a9ef01bdb5bb3a41d37aaaaf392f2a00185d238 languageName: node linkType: hard -"@opentelemetry/instrumentation-amqplib@npm:^0.49.0": - version: 0.49.0 - resolution: "@opentelemetry/instrumentation-amqplib@npm:0.49.0" - dependencies: - "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/891f201f476d77b960b6f3dc0186b3af52f7c249b69da365da184b01cf2056052fa93f85196e8811156908b2f532adf6a13773f102b4a78626046275827df2b1 - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-aws-lambda@npm:^0.53.1": - version: 0.53.1 - resolution: "@opentelemetry/instrumentation-aws-lambda@npm:0.53.1" - dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - "@types/aws-lambda": "npm:8.10.150" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/a305988e8332e416c5fd81ffbdad71ffd68eeb623c55ea26fa25212f3c0c254ce30022461067981e4a7c5cfb1e25d1f1ed306d09f09d68c17fbbe693ec2ed4a1 - languageName: node - linkType: hard - -"@opentelemetry/instrumentation-aws-sdk@npm:^0.55.0": +"@opentelemetry/instrumentation-amqplib@npm:^0.55.0": version: 0.55.0 - resolution: "@opentelemetry/instrumentation-aws-sdk@npm:0.55.0" + resolution: "@opentelemetry/instrumentation-amqplib@npm:0.55.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/propagation-utils": "npm:^0.31.2" + "@opentelemetry/instrumentation": "npm:^0.208.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/03e028c4dfe9a8a9372f6ef6d4c4520343a3ec571e2e04dcd29e684cf25d212f6331be268656006220b476f6b46748aba2d142b7b4c62efd24baebf6a3f1b77b + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-aws-lambda@npm:^0.61.0": + version: 0.61.0 + resolution: "@opentelemetry/instrumentation-aws-lambda@npm:0.61.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@types/aws-lambda": "npm:^8.10.155" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/5d664e1ea663bd68a769eeaf58d23c3a74efe09e5f95d523fa758455f670909a835445f4c6816de70c6c481a3d080ccdd94fb7d8f4eadf5062314f12903ffd81 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-aws-sdk@npm:^0.64.0": + version: 0.64.0 + resolution: "@opentelemetry/instrumentation-aws-sdk@npm:0.64.0" + dependencies: + "@opentelemetry/core": "npm:^2.0.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" "@opentelemetry/semantic-conventions": "npm:^1.34.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/c2ce4063f4761baa6e12d1830270952d67d9bd0fdba3d15b97ec107df54d2a21c01be6960e0c2e3dcb2358bc57ba977b694e02c78a01c0f193377d0e4557dd7e + checksum: 10/7d53e9dee6058f940af1661392876a4be9ac4d4ca5d3b5fe60163672e4043fb1f848a677a7dbf39abdda887dfb31568061f57cb36795ef54ba10ee5260728f78 languageName: node linkType: hard -"@opentelemetry/instrumentation-bunyan@npm:^0.48.0": - version: 0.48.0 - resolution: "@opentelemetry/instrumentation-bunyan@npm:0.48.0" +"@opentelemetry/instrumentation-bunyan@npm:^0.54.0": + version: 0.54.0 + resolution: "@opentelemetry/instrumentation-bunyan@npm:0.54.0" dependencies: - "@opentelemetry/api-logs": "npm:^0.202.0" - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/api-logs": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" "@types/bunyan": "npm:1.8.11" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/c0147693e5e6202198448b0ca8b2550ca972d9a72a78b3c95edb1942b6e0a1b295351c205dbfc7562e11a50b815a64b3b920ceb474d6a59daa4cfbea01d8f621 + checksum: 10/563eeb0788c551e03947730586ee75a32f9fac28512fb97f109eccd582fe3c049d8642c9f1a638ca028215899d83c8cc6f0b3f0c36467c5e69e7160dd81c0bc4 languageName: node linkType: hard -"@opentelemetry/instrumentation-cassandra-driver@npm:^0.48.0": - version: 0.48.0 - resolution: "@opentelemetry/instrumentation-cassandra-driver@npm:0.48.0" +"@opentelemetry/instrumentation-cassandra-driver@npm:^0.54.0": + version: 0.54.0 + resolution: "@opentelemetry/instrumentation-cassandra-driver@npm:0.54.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/7a3b2ba1b472108f0b1e1e799d7f8f8d0eb10034378b4b06fbec0e1fc59e095f3e374dd7739047856d27f00d8706603db08cfb90aa37b427fce18b43b0731932 + checksum: 10/0e9f2ccb4682c9c5244cca26e17f6c553ea8dc73ecdbadbe9564911e137103e33fd3e653c75825325fa51d44b125eb6ba7e89ac49508dfaa14c02553ae3e8848 languageName: node linkType: hard -"@opentelemetry/instrumentation-connect@npm:^0.46.0": - version: 0.46.0 - resolution: "@opentelemetry/instrumentation-connect@npm:0.46.0" +"@opentelemetry/instrumentation-connect@npm:^0.52.0": + version: 0.52.0 + resolution: "@opentelemetry/instrumentation-connect@npm:0.52.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" "@types/connect": "npm:3.4.38" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/e5b96bde0b8fbcf2edc22ed3a5ac655758440e9a3f8c4f39ae3dcf88edda8be6dd0bb121b3cffcf8df8b53fb0aef3c5bc62549cf5c24439fb9ba15a03f93280c + checksum: 10/c526768a10a4f6c76b791e382edeae2217a7b3c448b1a3458ef34ca46a645b6cf67eeec79bae119e2005f5d766c8eabc03b16058da6e94300110d029157e7e95 languageName: node linkType: hard -"@opentelemetry/instrumentation-cucumber@npm:^0.17.1": - version: 0.17.1 - resolution: "@opentelemetry/instrumentation-cucumber@npm:0.17.1" +"@opentelemetry/instrumentation-cucumber@npm:^0.24.0": + version: 0.24.0 + resolution: "@opentelemetry/instrumentation-cucumber@npm:0.24.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.0.0 - checksum: 10/ef65ef03f4aa79a3b4ae223aebf714b62b7d96261680932df02ba4590685253d4e7bf44ac58109e39a951bb0217ffef11f12224ede62ce65ac93a46f2b65e517 + checksum: 10/fc9542beb91284eb42cd72fb5d18b8a183ccdc7b40ca1c5f04013aded6517a336544d6e83fadfff51dfffb2abf748f0dc546eddb67330bed773d7275038697df languageName: node linkType: hard -"@opentelemetry/instrumentation-dataloader@npm:^0.20.0": - version: 0.20.0 - resolution: "@opentelemetry/instrumentation-dataloader@npm:0.20.0" +"@opentelemetry/instrumentation-dataloader@npm:^0.26.0": + version: 0.26.0 + resolution: "@opentelemetry/instrumentation-dataloader@npm:0.26.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/4d9e5600644e5d941860d492be9bd1b3966acd69e281f491f151347d482464e1481f7ce8c6898e744e235d052c16868774b6353d606a58a3538b9e32d43c2b92 + checksum: 10/e3efde5515698fc25437d4e347cc829da1f82df95c0523149913030b53e4a359577ee988b66d5b0a3bd8230697cd785e983176c0b4bb14d001c64c8948d46c10 languageName: node linkType: hard -"@opentelemetry/instrumentation-dns@npm:^0.46.0": - version: 0.46.0 - resolution: "@opentelemetry/instrumentation-dns@npm:0.46.0" +"@opentelemetry/instrumentation-dns@npm:^0.52.0": + version: 0.52.0 + resolution: "@opentelemetry/instrumentation-dns@npm:0.52.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/68c72e76a5c53d7d49632fb0126c097aa717c173fc0a2502f4b03be382844fbcdcc385f49dab502853006e3e64555879e5c26a24d126a87d7825ad01be1a9f01 + checksum: 10/a6ff78cacbcc50ff3d4f805144223a0fc2fa7df2bea5c198511c2e1a546550dddef75f8b85b5ed78bf5ffa50d006e166ae1f19ab6a4125edbdab7cc92cb49bad languageName: node linkType: hard -"@opentelemetry/instrumentation-express@npm:^0.51.1": - version: 0.51.1 - resolution: "@opentelemetry/instrumentation-express@npm:0.51.1" +"@opentelemetry/instrumentation-express@npm:^0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/instrumentation-express@npm:0.57.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/a0ecb32a8599d826a032df0c5558d43bf5024b131b93e6e57c659f208e265d40bafbad15c46a5621c698e7b6383ef2a02f4600633d52ba2f4b6fb75c08eed631 + checksum: 10/66c10e878433d0e90bbf88dfbf28b23ecdbc3777326f46548c16c5062118b37c623011cef390a5ed3de2855f67f91587703581846f4e1e702ff3a068404f3e89 languageName: node linkType: hard -"@opentelemetry/instrumentation-fastify@npm:^0.47.1": - version: 0.47.1 - resolution: "@opentelemetry/instrumentation-fastify@npm:0.47.1" +"@opentelemetry/instrumentation-fastify@npm:^0.53.0": + version: 0.53.0 + resolution: "@opentelemetry/instrumentation-fastify@npm:0.53.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/89ffbe54deaa90a33deb873db24967f7e0380c4dc448703ed6b7abbadd345687902e3511e23c1af622ce55e9b70a12e724d3773ad61d7007e4d3d8a47615591d + checksum: 10/fdcedc2aeb316f7fd6af490280b766d95939bd70e1edad415b6b5f228378e1ee10828bfa0222bbe3bab04527a48567a98593445046aea584cdecb4e632c3d1cd languageName: node linkType: hard -"@opentelemetry/instrumentation-fs@npm:^0.22.0": - version: 0.22.0 - resolution: "@opentelemetry/instrumentation-fs@npm:0.22.0" +"@opentelemetry/instrumentation-fs@npm:^0.28.0": + version: 0.28.0 + resolution: "@opentelemetry/instrumentation-fs@npm:0.28.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/56090810d8c31949ddbd9f1e2c9f91ca38e7214ada60bc9abc72cfac94fb303e5cc905a63e0c1ab2b524aaf80e7a152a3ab286d792fe82bb4e814cd5eb1d2f16 + checksum: 10/f62c1256e3c47d958b296b57f8ab1d0b8b8851e9c3d6ad8691af514a33a2341ec1a9345d57a705a420a7a5757a297e03cc04c497a5701ac027a98b63b1a90ab1 languageName: node linkType: hard -"@opentelemetry/instrumentation-generic-pool@npm:^0.46.1": - version: 0.46.1 - resolution: "@opentelemetry/instrumentation-generic-pool@npm:0.46.1" +"@opentelemetry/instrumentation-generic-pool@npm:^0.52.0": + version: 0.52.0 + resolution: "@opentelemetry/instrumentation-generic-pool@npm:0.52.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/10f8cbf5d052c3cb895857390f6a53ec292a1d0e8ec82df94bfb6cd49a0d0a0db0ba66cbb4127298418be4b68444d048f70833257d37cad404edf17578d477e5 + checksum: 10/f94b27d84a4172f95f7a33fa967f2ad562d4dba3182b1e8b6658b4eceb8a26a678b407a232858378a034dd8ffdb4e66bd98b4a8861fe042be59af3e4b400244f languageName: node linkType: hard -"@opentelemetry/instrumentation-graphql@npm:^0.50.0": - version: 0.50.0 - resolution: "@opentelemetry/instrumentation-graphql@npm:0.50.0" +"@opentelemetry/instrumentation-graphql@npm:^0.56.0": + version: 0.56.0 + resolution: "@opentelemetry/instrumentation-graphql@npm:0.56.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/a359e0839381d74dacf0b3acc8c951686cf73d6201fd305583e1c58ed6f4dcf1421efa322075808ed9c9298af3e579cbe4ac275960ceb7aceb8639b7346b5990 + checksum: 10/7895e702484367ef9c26f7c79f0039ac9677133342253f3935e7203156cbc396dfd799ef84e4758e83c008ed8c6b6893a05fc1df2566cfaae65e3770485ad8c9 languageName: node linkType: hard -"@opentelemetry/instrumentation-grpc@npm:^0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/instrumentation-grpc@npm:0.202.0" +"@opentelemetry/instrumentation-grpc@npm:^0.208.0": + version: 0.208.0 + resolution: "@opentelemetry/instrumentation-grpc@npm:0.208.0" dependencies: - "@opentelemetry/instrumentation": "npm:0.202.0" + "@opentelemetry/instrumentation": "npm:0.208.0" "@opentelemetry/semantic-conventions": "npm:^1.29.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/4b59bb35de3b900e9fc5e9ba44d1828b6b4eea15f1ce65a63ad9e047cff5989a9d1d23180e8c221c7bd375c104ecd23f2e7650405e37cf8d8a46edda53d447ee + checksum: 10/8f5a75c1cac44c2e15963b0c14259de7a3f0320391d24f4bbda2830e9471481af84be7f8b978c679d6dd94d5dfbb96bbe94c4dd601f70fb7ed2df10de89c195a languageName: node linkType: hard -"@opentelemetry/instrumentation-hapi@npm:^0.49.0": - version: 0.49.0 - resolution: "@opentelemetry/instrumentation-hapi@npm:0.49.0" +"@opentelemetry/instrumentation-hapi@npm:^0.55.0": + version: 0.55.0 + resolution: "@opentelemetry/instrumentation-hapi@npm:0.55.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/2e2640e26ae193000b63fb62aea5dc03eff2c4896442361ebc7e3e482713166bad49616a5d3c157fb8dd0fab908d0c4412e24f4d806b071435890436ccf8fd9f + checksum: 10/575c059a2dbcd77256acfca897d2cc08968b4e7e9e41449dfd5de2ca761fa2a544aaa6f2a8213f7374a42026e1f0b7e612ce01337e43108631e20e19fec01c81 languageName: node linkType: hard -"@opentelemetry/instrumentation-http@npm:^0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/instrumentation-http@npm:0.202.0" +"@opentelemetry/instrumentation-http@npm:^0.208.0": + version: 0.208.0 + resolution: "@opentelemetry/instrumentation-http@npm:0.208.0" dependencies: - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/instrumentation": "npm:0.202.0" + "@opentelemetry/core": "npm:2.2.0" + "@opentelemetry/instrumentation": "npm:0.208.0" "@opentelemetry/semantic-conventions": "npm:^1.29.0" forwarded-parse: "npm:2.1.2" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/fb36be7a08a33d07bb51e8c8dc64be937117f03d93a1425bc9ddb041464da1f01914adc1bc8173a145b61a7fd8b6151db8e284db1bcce12135d6b27b01c2ad0a + checksum: 10/f5e11eb7054d6701ea44ecb90321558e524b253a7945d1539f9971f83c5dfc1d294a6ac632eb35d2f19309837f16f6c405958c0bd9c50a8751dd07ee0704ca4e languageName: node linkType: hard -"@opentelemetry/instrumentation-ioredis@npm:^0.50.1": - version: 0.50.1 - resolution: "@opentelemetry/instrumentation-ioredis@npm:0.50.1" +"@opentelemetry/instrumentation-ioredis@npm:^0.56.0": + version: 0.56.0 + resolution: "@opentelemetry/instrumentation-ioredis@npm:0.56.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/redis-common": "npm:^0.38.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/redis-common": "npm:^0.38.2" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/08e6aed4aa6d0ad0d1dd4a6d02b260f46d1ce5168b8dd1f6b2d3f8c71f0204378e1b52f175817e53cdb6f98841a6ddfc5ca2005592ea678f046316dc4edff281 + checksum: 10/8fd316eb94323fca62b71b51df4ef1dc0cf7879315f3006633d07cd9ea87bb9ff1315e216e6e61013f6ab84142593935d2920812794736ce4f0ca97da522ff09 languageName: node linkType: hard -"@opentelemetry/instrumentation-kafkajs@npm:^0.11.0": - version: 0.11.0 - resolution: "@opentelemetry/instrumentation-kafkajs@npm:0.11.0" +"@opentelemetry/instrumentation-kafkajs@npm:^0.18.0": + version: 0.18.0 + resolution: "@opentelemetry/instrumentation-kafkajs@npm:0.18.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" "@opentelemetry/semantic-conventions": "npm:^1.30.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/6998633cda7260fde928f8691482bdbf5478d7f4c7c0be4c199c366345525733ec87e94b0a38b5b65a719489998f377ad52a065ebe0eeafc010e237533f1ce55 + checksum: 10/e3b998d905dc6c87b542e6b7004e12eeac19903872fe3e7d4c17771f69843b721dd73d0b136b2f21207d4dec7e53fd17d5042cf27a0028ec3c3074c0861654fe languageName: node linkType: hard -"@opentelemetry/instrumentation-knex@npm:^0.47.0": - version: 0.47.0 - resolution: "@opentelemetry/instrumentation-knex@npm:0.47.0" +"@opentelemetry/instrumentation-knex@npm:^0.53.1": + version: 0.53.1 + resolution: "@opentelemetry/instrumentation-knex@npm:0.53.1" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" "@opentelemetry/semantic-conventions": "npm:^1.33.1" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/f3a753505fd6499d05022fbddb0977b781ad41a857df9cde7410273ca3ad9f2343afc6136138bbdde16e5358d77bba66e2ddf7f93d4dab878b6e5b85d72fc6d8 + checksum: 10/a9acaaefcf1f4523b47b33f8e5bd99dfc9f803aa7c8c3fc79e3cc497a022edeefa890905708eccbecd0dff37b8cce5543a6d17c61e565d87bfa9bd1de43e1526 languageName: node linkType: hard -"@opentelemetry/instrumentation-koa@npm:^0.50.2": - version: 0.50.2 - resolution: "@opentelemetry/instrumentation-koa@npm:0.50.2" +"@opentelemetry/instrumentation-koa@npm:^0.57.0": + version: 0.57.0 + resolution: "@opentelemetry/instrumentation-koa@npm:0.57.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/semantic-conventions": "npm:^1.36.0" peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/3598afe454d6df1861dd0b7f4a8b617f4d3b89789961577912151df9a2d159523490f25c3e8d03234b5faa62d5f8f8985e8a52b65122cae4c58016debf109edb + "@opentelemetry/api": ^1.9.0 + checksum: 10/7df972b4a5c6c8cad6e1fe6ab3a6ea391d4db09f0d3e2b0b46f0ffa0289df88fe2446e06c08744d65293aeaff2f28628d51f8dea5d97404907a49f9e567f50f6 languageName: node linkType: hard -"@opentelemetry/instrumentation-lru-memoizer@npm:^0.47.0": - version: 0.47.0 - resolution: "@opentelemetry/instrumentation-lru-memoizer@npm:0.47.0" +"@opentelemetry/instrumentation-lru-memoizer@npm:^0.53.0": + version: 0.53.0 + resolution: "@opentelemetry/instrumentation-lru-memoizer@npm:0.53.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/91fd11ce2f012b715a1bffb5c81ca5d681e5c2d9f638a2e459f2d4bead587ed755e438c234132484d6d030f0b3104a0747596708f51df109449cd970e965e6ae + checksum: 10/6f83abdb58e83fe87ce926236c54fbaaf49f64f8149e455b1dc1f33d735faf575e6f6d4df36b8eadf176a74d01cd05c6f3a3babff56a5a7448744e0cba39df5e languageName: node linkType: hard -"@opentelemetry/instrumentation-memcached@npm:^0.46.0": - version: 0.46.0 - resolution: "@opentelemetry/instrumentation-memcached@npm:0.46.0" +"@opentelemetry/instrumentation-memcached@npm:^0.52.0": + version: 0.52.0 + resolution: "@opentelemetry/instrumentation-memcached@npm:0.52.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/semantic-conventions": "npm:^1.33.0" "@types/memcached": "npm:^2.2.6" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/12bbcc4b9dcc62b0b48ba83f555112881cc9723f174335bbf7695374ae17582fb12cb2f72295656c84608b73ef5a88afcf551d5c392afebddbf97d7a64230080 + checksum: 10/2288ebc16ab06313e18d5d3e12cae91c73515a17da6108c034506fe68dcadd96117c30cbcd89133131a02ea28aae33269c73b509db17cddb30f42697e482e1dc languageName: node linkType: hard -"@opentelemetry/instrumentation-mongodb@npm:^0.55.1": - version: 0.55.1 - resolution: "@opentelemetry/instrumentation-mongodb@npm:0.55.1" +"@opentelemetry/instrumentation-mongodb@npm:^0.61.0": + version: 0.61.0 + resolution: "@opentelemetry/instrumentation-mongodb@npm:0.61.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/233822b1886b5693923430fb126363005424bcabd03681bf2cf4c035a52dbc6475718417b2a284d38f2fdcac1423625f5a25b767018a3686e9da54f4313f4da9 + checksum: 10/fe5eb79c8924aa268c863af4a5878c47ba5730622cb861899802144a4dfe7431e7af6e9b568dd205fdedfd77fbbfe95539cdcf35e6b326ba695eeaadd61240ed languageName: node linkType: hard -"@opentelemetry/instrumentation-mongoose@npm:^0.49.0": - version: 0.49.0 - resolution: "@opentelemetry/instrumentation-mongoose@npm:0.49.0" +"@opentelemetry/instrumentation-mongoose@npm:^0.55.0": + version: 0.55.0 + resolution: "@opentelemetry/instrumentation-mongoose@npm:0.55.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/4b2b320346a0d00a56438c041ddda6cbd8b34d37de613daab618892fcba380b0cbd5396886028e9e2ac81c497f25e66f7668ccbf57c3f870678d30fc16e4ae24 + checksum: 10/5ee25946b7a6a50178ff6b50d5a88b8a189f0ba6effc45ee07e6026c353b1cad605f51cb9c61f9200e066833a7f3b588ede02582dc7dbbce4a9d715ec312cea6 languageName: node linkType: hard -"@opentelemetry/instrumentation-mysql2@npm:^0.48.1": - version: 0.48.1 - resolution: "@opentelemetry/instrumentation-mysql2@npm:0.48.1" +"@opentelemetry/instrumentation-mysql2@npm:^0.55.0": + version: 0.55.0 + resolution: "@opentelemetry/instrumentation-mysql2@npm:0.55.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - "@opentelemetry/sql-common": "npm:^0.41.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/semantic-conventions": "npm:^1.33.0" + "@opentelemetry/sql-common": "npm:^0.41.2" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/d52d0950201adb3c4ce8f82edc544eee489de0971045f1e907832d6e7424d372da3a1e62143f6fcde698d883dfbe7adbea243cef9653f467c96d011d98225676 + checksum: 10/ca5bb6c99bd8fd3590341955aeece3c69982e07b2df520998ea282ac9135311f8fc37ba55fb66adf07a80adcf98d42892b698422c9817667231064cc251e626d languageName: node linkType: hard -"@opentelemetry/instrumentation-mysql@npm:^0.48.1": - version: 0.48.1 - resolution: "@opentelemetry/instrumentation-mysql@npm:0.48.1" +"@opentelemetry/instrumentation-mysql@npm:^0.54.0": + version: 0.54.0 + resolution: "@opentelemetry/instrumentation-mysql@npm:0.54.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" "@types/mysql": "npm:2.15.27" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/8e4493e5a685d98f0b7797b6f3996f7949cdae758e68c12e2edb57d8388be47a67e668c12340f1a2e18c692c8db990a765913120e402215c8d473695c9a281b8 + checksum: 10/d4756b4d72c0232f92fc00fd8fb03d02fe27d9108c8c74ae1d8aa50800207f7e6dc10aa8f5a636d9b6c8865b10e10a79741242c08af4d4f0599977182b7508ae languageName: node linkType: hard -"@opentelemetry/instrumentation-nestjs-core@npm:^0.48.1": - version: 0.48.1 - resolution: "@opentelemetry/instrumentation-nestjs-core@npm:0.48.1" +"@opentelemetry/instrumentation-nestjs-core@npm:^0.55.0": + version: 0.55.0 + resolution: "@opentelemetry/instrumentation-nestjs-core@npm:0.55.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" "@opentelemetry/semantic-conventions": "npm:^1.30.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/fbbd5dd411b79371725e9075bca0c492a524ebd90d5e17434c3608bae6ec1cbbd5cca4dc0385c423e59963bfc1b945efdff5cd02f09a3f2a06391fc176d52c50 + checksum: 10/6c930d4b670f3a1764d11539867656594fa1a74d47597ca0b4fdf0425403db14552cf41a28ae4169d2034680f9d968a1c90cb5bfe2a6504bf101eaef026cc2fe languageName: node linkType: hard -"@opentelemetry/instrumentation-net@npm:^0.46.1": - version: 0.46.1 - resolution: "@opentelemetry/instrumentation-net@npm:0.46.1" +"@opentelemetry/instrumentation-net@npm:^0.52.0": + version: 0.52.0 + resolution: "@opentelemetry/instrumentation-net@npm:0.52.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/3372ac7b8643c4adea2d8ffd762a2c872e02d000d5cc5afca922f422033eea251adeeac1c5d1bd49014d76c9167a0dfa98cd41dda7b3950395c179a8a8c6de81 + checksum: 10/af37496c50f9cd893e3552e060528cd5433a87fb25f55dbc7eca298e68c249b7eafe04bc614bd9bc03e75580713ac1b80b4889c595f5951735fd7e00b6f985fe languageName: node linkType: hard -"@opentelemetry/instrumentation-oracledb@npm:^0.28.0": - version: 0.28.0 - resolution: "@opentelemetry/instrumentation-oracledb@npm:0.28.0" +"@opentelemetry/instrumentation-openai@npm:^0.7.0": + version: 0.7.0 + resolution: "@opentelemetry/instrumentation-openai@npm:0.7.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/api-logs": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/semantic-conventions": "npm:^1.36.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/d480956b11f8b437c538b42bd57d4ae3ec8201ac88d7b706c7503c34d27d1af2084672fcad8e576bfe07c817adc13f07cba4ee2c7bbef5af7b628a4c80081beb + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-oracledb@npm:^0.34.0": + version: 0.34.0 + resolution: "@opentelemetry/instrumentation-oracledb@npm:0.34.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/semantic-conventions": "npm:^1.34.0" "@types/oracledb": "npm:6.5.2" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/820a6e760d2137ad00b27ff78014316fc37e92f2be94ad4c6082e92ace8fcd89a6d2bcc744e6330358ebd0f127d7993b65843b167b0c1ea0f8d5cb0fe201c634 + checksum: 10/181937a78f28f79a378d427624a1cbc6d02127a88310147ca741d47d8856312d2802b608058238330f2876b0a817f548255c71c1d1b816c9ba3cf52d1b9a2e67 languageName: node linkType: hard -"@opentelemetry/instrumentation-pg@npm:^0.54.1": - version: 0.54.1 - resolution: "@opentelemetry/instrumentation-pg@npm:0.54.1" +"@opentelemetry/instrumentation-pg@npm:^0.61.1": + version: 0.61.1 + resolution: "@opentelemetry/instrumentation-pg@npm:0.61.1" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - "@opentelemetry/sql-common": "npm:^0.41.0" - "@types/pg": "npm:8.15.4" + "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/semantic-conventions": "npm:^1.34.0" + "@opentelemetry/sql-common": "npm:^0.41.2" + "@types/pg": "npm:8.15.6" "@types/pg-pool": "npm:2.0.6" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/eb88b8bd4417db8188af44e62659a7bc35b3f6a4b155d12c69357fc88185f3d719b3dd93e5562896088d07041c882a71a06d9672c14332846ae4f5abd26ec99a + checksum: 10/de9c4e08d8754ee2cbb964c76d2bde981706fae6ece7ef9688f66ff23308b185c7a6b55d79c25fedf377ba1deb95818b165d6296e920122c5f7ec00fa344c0b0 languageName: node linkType: hard -"@opentelemetry/instrumentation-pino@npm:^0.49.1": - version: 0.49.1 - resolution: "@opentelemetry/instrumentation-pino@npm:0.49.1" +"@opentelemetry/instrumentation-pino@npm:^0.55.0": + version: 0.55.0 + resolution: "@opentelemetry/instrumentation-pino@npm:0.55.0" dependencies: - "@opentelemetry/api-logs": "npm:^0.202.0" + "@opentelemetry/api-logs": "npm:^0.208.0" "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/2e2bff24bef74f68da8f4b4655d198c3fba7f7425f2f6317077385bd34dfbfb44c3747165859fec7a9f90aa0c77f830148e112e3d8257f6ac35dc28bbe157041 + checksum: 10/238447b943125d4ff44b7aa340818e7f7366f2a927651576c0dcfe48bfb9b7b6578462b887fbc59e4083e8d0f1d63e3c9c9e394acd78b385547d3ebc368d9c3f languageName: node linkType: hard -"@opentelemetry/instrumentation-redis@npm:^0.50.0": - version: 0.50.0 - resolution: "@opentelemetry/instrumentation-redis@npm:0.50.0" +"@opentelemetry/instrumentation-redis@npm:^0.57.1": + version: 0.57.1 + resolution: "@opentelemetry/instrumentation-redis@npm:0.57.1" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/redis-common": "npm:^0.38.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/redis-common": "npm:^0.38.2" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/94a05b1401fa071c2483d241a0cffb3fc79492c44ee7a2d2e320118cbc948eb7972c70e93f128bae29febd4958fc95904afff56b5c49da8b5407666e8f26e748 + checksum: 10/66e15fbc41edcf21500dd1fab6a5fddd3d59e7ef5a4f12ac8403bec3cdd51b5b8b3ab7555041b50c42463827fae8a0a77e5fdcc3bb0e155de75278ec3379fdc1 languageName: node linkType: hard -"@opentelemetry/instrumentation-restify@npm:^0.48.2": - version: 0.48.2 - resolution: "@opentelemetry/instrumentation-restify@npm:0.48.2" +"@opentelemetry/instrumentation-restify@npm:^0.54.0": + version: 0.54.0 + resolution: "@opentelemetry/instrumentation-restify@npm:0.54.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/b2a0917248ae9defaff8dd9d5c650fb627241b56355474a776a54fd9541a29b3bbb487b54dba7042ac2f7e15cb0a79f738360fe8a3322e4066ff06728e027292 + checksum: 10/5488aaf97dcc5e1ec8f9c055258c587553f20f825b8d7c5dcd2a7398adf85554f1d8b5d1c35235c98280ab6662917b12d148215c4d3882e205314883c9bed0b8 languageName: node linkType: hard -"@opentelemetry/instrumentation-router@npm:^0.47.0": - version: 0.47.0 - resolution: "@opentelemetry/instrumentation-router@npm:0.47.0" +"@opentelemetry/instrumentation-router@npm:^0.53.0": + version: 0.53.0 + resolution: "@opentelemetry/instrumentation-router@npm:0.53.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/620a54e97e311692c197e86f12bf43bf1a04f2b3009f71dfdf6e0972b5c282606cc649739ba0041f60aea3fae9b027b315378e71a5f764c85952ece2e5b28078 + checksum: 10/3a24e2cd25b0bff9e96c1956fe9bb2fbda8aa807125cb58535fb16e4e10a65ac391a2db748bc50a1ca7e35fcfbc26e078a3fc96e0f105f813b06f57079f84062 languageName: node linkType: hard -"@opentelemetry/instrumentation-runtime-node@npm:^0.16.0": - version: 0.16.0 - resolution: "@opentelemetry/instrumentation-runtime-node@npm:0.16.0" +"@opentelemetry/instrumentation-runtime-node@npm:^0.22.0": + version: 0.22.0 + resolution: "@opentelemetry/instrumentation-runtime-node@npm:0.22.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/85d5a1302558fc99d5ec411ef4ea7306bb2c5295807ef3e75f61272b46be1752a19de145d22dbe530803ddfef65a4811ecccb5944183302ff60b8762005de70d + checksum: 10/20a45189e0bffb08e9ef111fc4be33e98309948aaa10b448979bd900b0c53a752606366b44fa2351ba2fdc2cf775ba4eaf03cb038ba31798c679180ea987dba8 languageName: node linkType: hard -"@opentelemetry/instrumentation-socket.io@npm:^0.49.0": - version: 0.49.0 - resolution: "@opentelemetry/instrumentation-socket.io@npm:0.49.0" +"@opentelemetry/instrumentation-socket.io@npm:^0.55.0": + version: 0.55.0 + resolution: "@opentelemetry/instrumentation-socket.io@npm:0.55.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/16ab1664c3692267a6b9d0fdaf9256d66d9b705c056c019fb15d261d1f09296d325ddbbc9402b60b0d3b5722b55cdb6c7d0980c8ee965ef4346461742cdf87a1 + checksum: 10/7d2b5283d055283f49a9c90ae0f8a473bac2fbbe0088d252932099e3962859dda31230c79563a46cab030bf25f5384761b3de987f4cf6979b40d66ca0e0bdf26 languageName: node linkType: hard -"@opentelemetry/instrumentation-tedious@npm:^0.21.1": - version: 0.21.1 - resolution: "@opentelemetry/instrumentation-tedious@npm:0.21.1" +"@opentelemetry/instrumentation-tedious@npm:^0.27.0": + version: 0.27.0 + resolution: "@opentelemetry/instrumentation-tedious@npm:0.27.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.202.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" "@types/tedious": "npm:^4.0.14" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/5347d20e2cb4e07a1d25275edb25c47dcf87700a19de630fb33b7fc06bda0eb5934fb058fcb15b33be2eeb49a7430720c011f8540405b5ee10d22e689c93b94a + checksum: 10/453492b72cf16b855da1a4ba38b9619261ef77ed4afe8ae551c0abc529c81c8640d816934aae197fa719e8e79591613d2398a8a7f3b77c82de41bf51db3eebfc languageName: node linkType: hard -"@opentelemetry/instrumentation-undici@npm:^0.13.2": - version: 0.13.2 - resolution: "@opentelemetry/instrumentation-undici@npm:0.13.2" +"@opentelemetry/instrumentation-undici@npm:^0.19.0": + version: 0.19.0 + resolution: "@opentelemetry/instrumentation-undici@npm:0.19.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" + "@opentelemetry/semantic-conventions": "npm:^1.24.0" peerDependencies: "@opentelemetry/api": ^1.7.0 - checksum: 10/9f806706096d629796d1205a0f2a9f18cf3542f61dfb60f20199ca5ba269044145a593f5ee0ad495b046eb01b8a14448a61a8bbe6a18611f6967f3473923d97e + checksum: 10/862ea5e49c2cf38c7a135f5ea6a95f1b8ca009620f9b0e4d6c7ffb69e8e1d4e7d4169e4cf0cd2af4ce574864b37ea3f7c039b0e6600d3e490f3c263a8647325e languageName: node linkType: hard -"@opentelemetry/instrumentation-winston@npm:^0.47.0": - version: 0.47.0 - resolution: "@opentelemetry/instrumentation-winston@npm:0.47.0" +"@opentelemetry/instrumentation-winston@npm:^0.53.0": + version: 0.53.0 + resolution: "@opentelemetry/instrumentation-winston@npm:0.53.0" dependencies: - "@opentelemetry/api-logs": "npm:^0.202.0" - "@opentelemetry/instrumentation": "npm:^0.202.0" + "@opentelemetry/api-logs": "npm:^0.208.0" + "@opentelemetry/instrumentation": "npm:^0.208.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/1a046490c1509f77ae70552ef6cdbf6c21baba4f5d6f45e0c473ffa102156b009acd20036dafbf5d29e92132df88c4aad078b62606af3bda0fbdd4ab90073937 + checksum: 10/5c3efd7c9cb074718cf4de99c27acf69bbb35bdf16d74f92dbdd8fd4efba2c637e3b234155e335b473d5caa060a6f40a49136d808017547d3a4d5eab66eed9b7 languageName: node linkType: hard -"@opentelemetry/instrumentation@npm:0.202.0, @opentelemetry/instrumentation@npm:^0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/instrumentation@npm:0.202.0" +"@opentelemetry/instrumentation@npm:0.208.0, @opentelemetry/instrumentation@npm:^0.208.0": + version: 0.208.0 + resolution: "@opentelemetry/instrumentation@npm:0.208.0" dependencies: - "@opentelemetry/api-logs": "npm:0.202.0" - import-in-the-middle: "npm:^1.8.1" - require-in-the-middle: "npm:^7.1.1" + "@opentelemetry/api-logs": "npm:0.208.0" + import-in-the-middle: "npm:^2.0.0" + require-in-the-middle: "npm:^8.0.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/da1db1ebc4ca847cc68d894b2e3a6c6552851d93af8ea793d42474e920f710664575c8991dc269e1a83fcf8f5abdda2cc724fa24e9cc4ae19fa6f70eb68ffc0e + checksum: 10/0591121c1bab29b8246ba879b1ed91f2db17680cfce56a635bf2e81390a9140f029b094ff4498ff154132379192bf424d7d234d2114883735603e4a6581a4a79 languageName: node linkType: hard -"@opentelemetry/instrumentation@npm:0.54.2": - version: 0.54.2 - resolution: "@opentelemetry/instrumentation@npm:0.54.2" +"@opentelemetry/otlp-exporter-base@npm:0.208.0": + version: 0.208.0 + resolution: "@opentelemetry/otlp-exporter-base@npm:0.208.0" dependencies: - "@opentelemetry/api-logs": "npm:0.54.2" - "@types/shimmer": "npm:^1.2.0" - import-in-the-middle: "npm:^1.8.1" - require-in-the-middle: "npm:^7.1.1" - semver: "npm:^7.5.2" - shimmer: "npm:^1.2.1" + "@opentelemetry/core": "npm:2.2.0" + "@opentelemetry/otlp-transformer": "npm:0.208.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/1c570fb2e55d2ea7dcc45103afb53ffc331efb675dc404783639c0ed4c93e4e0fa04751672f75ca2a633ca03943e520cf802ee0291e79fa33be54a097af46fc6 + checksum: 10/d24e1e766a8059861232fd338be7d65bded5167176b4c7c1be9a1833167f73fd352392a1df28ca002734bdbe00a70d06d1e7daeb97d9d7f71f85dc310a831a42 languageName: node linkType: hard -"@opentelemetry/otlp-exporter-base@npm:0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/otlp-exporter-base@npm:0.202.0" - dependencies: - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/otlp-transformer": "npm:0.202.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/229778895ba1971451a8b1ec8a4787b0cdba337c87f3ca6aaf6d649f5e2b26439ba1befa36d31824fffa3cef62382792680ec70194f57aac6ebe98e95acecc69 - languageName: node - linkType: hard - -"@opentelemetry/otlp-exporter-base@npm:0.54.2": - version: 0.54.2 - resolution: "@opentelemetry/otlp-exporter-base@npm:0.54.2" - dependencies: - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/otlp-transformer": "npm:0.54.2" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/f4eb3009ab87c072b52d33106b60a507b6debbc40ad6762275f6bb48144e1dc401d1100ea87540df08dbdb542f4ea6779aa805313d0b3966eae34ca3dded1436 - languageName: node - linkType: hard - -"@opentelemetry/otlp-grpc-exporter-base@npm:0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/otlp-grpc-exporter-base@npm:0.202.0" +"@opentelemetry/otlp-grpc-exporter-base@npm:0.208.0": + version: 0.208.0 + resolution: "@opentelemetry/otlp-grpc-exporter-base@npm:0.208.0" dependencies: "@grpc/grpc-js": "npm:^1.7.1" - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/otlp-exporter-base": "npm:0.202.0" - "@opentelemetry/otlp-transformer": "npm:0.202.0" + "@opentelemetry/core": "npm:2.2.0" + "@opentelemetry/otlp-exporter-base": "npm:0.208.0" + "@opentelemetry/otlp-transformer": "npm:0.208.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/ba52acee1d46c3bca38d6eba8fea41d5c7389c26a34dd7ed4f4fbc46da5fcbf109ae139c5149c9730cd076ba51d4933d9bc06c223470ee8a463a31fed75b4ed6 + checksum: 10/f88ee3d2377a27ee41077b5cb79b122ee8f8fa95cab9457ca53f8ef9b92b688b3c455b05c80b55ff3bf23d254eb596c361089fdf4a84b4f0317fc7e059fb4790 languageName: node linkType: hard -"@opentelemetry/otlp-grpc-exporter-base@npm:0.54.2": - version: 0.54.2 - resolution: "@opentelemetry/otlp-grpc-exporter-base@npm:0.54.2" +"@opentelemetry/otlp-transformer@npm:0.208.0": + version: 0.208.0 + resolution: "@opentelemetry/otlp-transformer@npm:0.208.0" dependencies: - "@grpc/grpc-js": "npm:^1.7.1" - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/otlp-exporter-base": "npm:0.54.2" - "@opentelemetry/otlp-transformer": "npm:0.54.2" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/d7647ff72fab06ed410f3782c0199a24a98da61bcc5fbc19aa8afcd2fac2b376aad48f9206504d40bee6070de3a7e4bf03423d438a3daaa3f71c3a95f261f48b - languageName: node - linkType: hard - -"@opentelemetry/otlp-transformer@npm:0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/otlp-transformer@npm:0.202.0" - dependencies: - "@opentelemetry/api-logs": "npm:0.202.0" - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/resources": "npm:2.0.1" - "@opentelemetry/sdk-logs": "npm:0.202.0" - "@opentelemetry/sdk-metrics": "npm:2.0.1" - "@opentelemetry/sdk-trace-base": "npm:2.0.1" + "@opentelemetry/api-logs": "npm:0.208.0" + "@opentelemetry/core": "npm:2.2.0" + "@opentelemetry/resources": "npm:2.2.0" + "@opentelemetry/sdk-logs": "npm:0.208.0" + "@opentelemetry/sdk-metrics": "npm:2.2.0" + "@opentelemetry/sdk-trace-base": "npm:2.2.0" protobufjs: "npm:^7.3.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/67e189af60bf8308a5b93deb85bef9709a5604b8c7915d0d42a7a2bff932b7257dfdfc071812c77913e136955a985ffe838c72b5f10059021087ea0bc52d84cd + checksum: 10/867a16a7a723a3df7a7ea8fa9f3c976139e32954efd1146812486cbee20c5eea73ba03841c64312b049e998a7e678589ddef80f7a04aaf95f649753eebe79e3d languageName: node linkType: hard -"@opentelemetry/otlp-transformer@npm:0.54.2": - version: 0.54.2 - resolution: "@opentelemetry/otlp-transformer@npm:0.54.2" - dependencies: - "@opentelemetry/api-logs": "npm:0.54.2" - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/resources": "npm:1.27.0" - "@opentelemetry/sdk-logs": "npm:0.54.2" - "@opentelemetry/sdk-metrics": "npm:1.27.0" - "@opentelemetry/sdk-trace-base": "npm:1.27.0" - protobufjs: "npm:^7.3.0" - peerDependencies: - "@opentelemetry/api": ^1.3.0 - checksum: 10/316fcdeca02666dfb2919746a83b1523f729875efac45075d513789fc534a3aa4a467c9c519c7fd8a16db78274534373bee0f56f95c89ba09dc4868e2fb42c10 - languageName: node - linkType: hard - -"@opentelemetry/propagation-utils@npm:^0.31.2": - version: 0.31.2 - resolution: "@opentelemetry/propagation-utils@npm:0.31.2" - peerDependencies: - "@opentelemetry/api": ^1.0.0 - checksum: 10/13828caae31651a1c4fa736f254b49a46b62348ff9ece945b19253fb52562db08bd268b5b0944ef3d2cb951f20cd4118ba14158b34a192e63868f0f3aa9c7cc9 - languageName: node - linkType: hard - -"@opentelemetry/propagator-b3@npm:1.27.0": - version: 1.27.0 - resolution: "@opentelemetry/propagator-b3@npm:1.27.0" - dependencies: - "@opentelemetry/core": "npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/21af4d3416155071d49351087f16ba66d255fba33f7bf6ffded538646e5a2efc53228733466c4419761a8f83aa200a940d6e30a27cdcb45ebd2665351ef4175e - languageName: node - linkType: hard - -"@opentelemetry/propagator-b3@npm:2.0.1": - version: 2.0.1 - resolution: "@opentelemetry/propagator-b3@npm:2.0.1" - dependencies: - "@opentelemetry/core": "npm:2.0.1" - peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/9871854b3d7516273c1f867c7a6a8eac5791f77954edf7509cc320ee6c109de8ef44778076edd4a64421b84f31632e4d283cb685f9220c5e7f1fe823c0e81a06 - languageName: node - linkType: hard - -"@opentelemetry/propagator-jaeger@npm:1.27.0": - version: 1.27.0 - resolution: "@opentelemetry/propagator-jaeger@npm:1.27.0" - dependencies: - "@opentelemetry/core": "npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/896cf18c3278083caec05b63a622b0e3da9abf658670f9170c4c47fcc3a121d878f7c6708b012490bd466e39f361a58773631fbc4784b4c03ad680002c1df50d - languageName: node - linkType: hard - -"@opentelemetry/propagator-jaeger@npm:2.0.1": - version: 2.0.1 - resolution: "@opentelemetry/propagator-jaeger@npm:2.0.1" - dependencies: - "@opentelemetry/core": "npm:2.0.1" - peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/d6a51e7dd58dde7ada4c0fe510a14f9a33f1972f0db11b64fccb867be23652d4d8a55012dd5a295753ad27c456b21d502eb3a24dfbce6a0a3304e1fa6e1f4896 - languageName: node - linkType: hard - -"@opentelemetry/redis-common@npm:^0.38.0": - version: 0.38.0 - resolution: "@opentelemetry/redis-common@npm:0.38.0" - checksum: 10/c7caa450ed27ad02aeefa3e4d643e7d065d76e5f1cd275c8c3d0c7d324bf7d06434666dbd836d219f6b2e1057285f30ccaa89b949438b38118a70786ec31fa52 - languageName: node - linkType: hard - -"@opentelemetry/resource-detector-alibaba-cloud@npm:^0.31.2": - version: 0.31.2 - resolution: "@opentelemetry/resource-detector-alibaba-cloud@npm:0.31.2" - dependencies: - "@opentelemetry/core": "npm:^2.0.0" - "@opentelemetry/resources": "npm:^2.0.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - peerDependencies: - "@opentelemetry/api": ^1.0.0 - checksum: 10/1e2498d5dc970addaeb88c9a1c4be85248438fe69876f2223ee526535ccb50d1ffaca366bfc5c03d0c84933050fd5d821726d8ce92ac5829da18f4a91025ce2f - languageName: node - linkType: hard - -"@opentelemetry/resource-detector-aws@npm:^2.2.0": +"@opentelemetry/propagator-b3@npm:2.2.0": version: 2.2.0 - resolution: "@opentelemetry/resource-detector-aws@npm:2.2.0" + resolution: "@opentelemetry/propagator-b3@npm:2.2.0" + dependencies: + "@opentelemetry/core": "npm:2.2.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/567a1bdd74cd81008fc5e4289c5a870daf5c46326a7c21e9dfd7e462e42c033dd51e7acd8c7b206926b5e78d1a4e3d72d3b13c1ce290ecec984730e847d4e122 + languageName: node + linkType: hard + +"@opentelemetry/propagator-jaeger@npm:2.2.0": + version: 2.2.0 + resolution: "@opentelemetry/propagator-jaeger@npm:2.2.0" + dependencies: + "@opentelemetry/core": "npm:2.2.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/7da76cab4387cd52723865ccee055dafeed1763c784ea6e13152f1264ad3ccd5b0cace442dac00755eeb17d9af216d0e235aeb6c739a9703e1b1d14b3bf1f5f8 + languageName: node + linkType: hard + +"@opentelemetry/redis-common@npm:^0.38.2": + version: 0.38.2 + resolution: "@opentelemetry/redis-common@npm:0.38.2" + checksum: 10/2a4f992572b1990a407ac92c7db941aecb6e8d71f034f4ea0b00b2b1739ad07c22767198a6759ab634cbbe9eebfbea062e2b79a25484289c226c665558041503 + languageName: node + linkType: hard + +"@opentelemetry/resource-detector-alibaba-cloud@npm:^0.31.11": + version: 0.31.11 + resolution: "@opentelemetry/resource-detector-alibaba-cloud@npm:0.31.11" + dependencies: + "@opentelemetry/core": "npm:^2.0.0" + "@opentelemetry/resources": "npm:^2.0.0" + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 10/63aedc54a9250aa249749d629ac3be42c99cf9a64c0735b78cd2d85d3d2ae5c9db7371909673f494cd70ddd3dae174a89e065e220845bf79e8937049ced24c44 + languageName: node + linkType: hard + +"@opentelemetry/resource-detector-aws@npm:^2.8.0": + version: 2.8.0 + resolution: "@opentelemetry/resource-detector-aws@npm:2.8.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" "@opentelemetry/resources": "npm:^2.0.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.0.0 - checksum: 10/91e6b67936d6846ba98b31438a9f32e5486f27153cfcd57978c8b6c5df9cc400c821ea63b06fff158c6a7e97b3ded3396f91f02410f28d4113d6b5bdf75f2bec + checksum: 10/7249d3a39f1039b6c6998f7e2f7792566fbff1b6604e9c538cbadc6dac8a769c35ece8fa0d1fcd92721288b81b3e8ac241101f35057f635c765cda7e5f468b38 languageName: node linkType: hard -"@opentelemetry/resource-detector-azure@npm:^0.9.0": - version: 0.9.0 - resolution: "@opentelemetry/resource-detector-azure@npm:0.9.0" +"@opentelemetry/resource-detector-azure@npm:^0.16.0": + version: 0.16.0 + resolution: "@opentelemetry/resource-detector-azure@npm:0.16.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" "@opentelemetry/resources": "npm:^2.0.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/semantic-conventions": "npm:^1.37.0" peerDependencies: "@opentelemetry/api": ^1.0.0 - checksum: 10/93d85781e73f37ab186b8d39ad144c944d80e69d19361ef4f846499e2bf0ba27df2cd00e334b1408415c80488f1633764696e1d29b639ed7829631ec29c92286 + checksum: 10/06fc137149d2f2fe231665f25af31542ae21f74b4682a615034439e84d06ae855172a4c21a980278ceda867faa0785140c2d90e59441ac92e36344da5f70cee1 languageName: node linkType: hard -"@opentelemetry/resource-detector-container@npm:^0.7.2": - version: 0.7.2 - resolution: "@opentelemetry/resource-detector-container@npm:0.7.2" +"@opentelemetry/resource-detector-container@npm:^0.7.11": + version: 0.7.11 + resolution: "@opentelemetry/resource-detector-container@npm:0.7.11" dependencies: "@opentelemetry/core": "npm:^2.0.0" "@opentelemetry/resources": "npm:^2.0.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.0.0 - checksum: 10/d556c4f64737726e31f211c6163ea359e403b4b0c65bca9fff565ec6a579a18340f4c6dc68d78ac707413c44bcb5f2a362eab2794fbd99524faf161517793e0e + checksum: 10/3372f6f6df98467181fb2f794493eb35d7067d330273ec4f19b16fbeae55d897360e1ce44dc0459112f1b4a65c97411d5f3f53530cb882b3031d1195dfd4ac41 languageName: node linkType: hard -"@opentelemetry/resource-detector-gcp@npm:^0.36.0": - version: 0.36.0 - resolution: "@opentelemetry/resource-detector-gcp@npm:0.36.0" +"@opentelemetry/resource-detector-gcp@npm:^0.43.0": + version: 0.43.0 + resolution: "@opentelemetry/resource-detector-gcp@npm:0.43.0" dependencies: "@opentelemetry/core": "npm:^2.0.0" "@opentelemetry/resources": "npm:^2.0.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" gcp-metadata: "npm:^6.0.0" peerDependencies: "@opentelemetry/api": ^1.0.0 - checksum: 10/9bfffd5d21a90509bc8d4dd3ae27554dd56719ca1607fcc20900034cc5776ff4c0025a3d4fddbd7c12e7962dd747516bdca9a9da6ccc59223c8d485609aea732 + checksum: 10/6fe63ac24b454ec2bb337aeceaba732f74796d36e6a08e9613982bc6d982f7627a910bb953e949dd4cef054fbd4d9b3261dc1a98d75070060a90cfce388ab985 languageName: node linkType: hard -"@opentelemetry/resources@npm:1.27.0": - version: 1.27.0 - resolution: "@opentelemetry/resources@npm:1.27.0" +"@opentelemetry/resources@npm:2.2.0, @opentelemetry/resources@npm:^2.0.0": + version: 2.2.0 + resolution: "@opentelemetry/resources@npm:2.2.0" dependencies: - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/semantic-conventions": "npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/654141ea65854bba84c22eeecc5af0054f14462f2664f36ac1ad8a170404e3218fccb98cafaaff4ec45e85523230e58eafbf222c25d00de8a60141ce77a34bbf - languageName: node - linkType: hard - -"@opentelemetry/resources@npm:2.0.1, @opentelemetry/resources@npm:^2.0.0": - version: 2.0.1 - resolution: "@opentelemetry/resources@npm:2.0.1" - dependencies: - "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/core": "npm:2.2.0" "@opentelemetry/semantic-conventions": "npm:^1.29.0" peerDependencies: "@opentelemetry/api": ">=1.3.0 <1.10.0" - checksum: 10/282f3831de2755d0fda2d8b6e37f9587ea248066d50c7d2f14c803ac9d5262a0f1db98a4185bcdc5acaeeece0b61f4fce43bc3896a79f1da79045ae4928618bf + checksum: 10/65ccdb1de957dc89aef252cf84b73cd0257ec44feec2b513fcf08e8c4d03e97275661d3f60c4b6134cee33ca4359a5ab6ef5d3a97339a3585aa997a381ef9098 languageName: node linkType: hard -"@opentelemetry/sdk-logs@npm:0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/sdk-logs@npm:0.202.0" +"@opentelemetry/sdk-logs@npm:0.208.0": + version: 0.208.0 + resolution: "@opentelemetry/sdk-logs@npm:0.208.0" dependencies: - "@opentelemetry/api-logs": "npm:0.202.0" - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/resources": "npm:2.0.1" + "@opentelemetry/api-logs": "npm:0.208.0" + "@opentelemetry/core": "npm:2.2.0" + "@opentelemetry/resources": "npm:2.2.0" peerDependencies: "@opentelemetry/api": ">=1.4.0 <1.10.0" - checksum: 10/e1b76647282a41ad7004c86c0058b0e9be70fdcf34aa2761929401e274af79bc5e3c6610a63e14fde4d51d96d8c96adaf3247c10b9a64cb70086d2ede2e421ca + checksum: 10/8413cdbf3a072d79a569ca7bcf3c8b333dfb1cb11a7a8841a9bb5482d6ee58d5a3a26efa0b9f02bf410b3f9fa99995ee5aa58aab505de560a2b2ac0b114ce70d languageName: node linkType: hard -"@opentelemetry/sdk-logs@npm:0.54.2": - version: 0.54.2 - resolution: "@opentelemetry/sdk-logs@npm:0.54.2" +"@opentelemetry/sdk-metrics@npm:2.2.0": + version: 2.2.0 + resolution: "@opentelemetry/sdk-metrics@npm:2.2.0" dependencies: - "@opentelemetry/api-logs": "npm:0.54.2" - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/resources": "npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ">=1.4.0 <1.10.0" - checksum: 10/c00a01de156d529674ed9b607fbabffd3306aa51b7b1897a670214cab7ba7d07da1f2bc5e8d957b0f441448b8ec22ac0526b1b368e36a61edf2bd54bfe571d2d - languageName: node - linkType: hard - -"@opentelemetry/sdk-metrics@npm:1.27.0": - version: 1.27.0 - resolution: "@opentelemetry/sdk-metrics@npm:1.27.0" - dependencies: - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/resources": "npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ">=1.3.0 <1.10.0" - checksum: 10/0d6061f42879170e4b4cf4847aa658520a7574f287b33562299ddeb97b0145a4ec91abec63d2292af0d632fce3e63b42f6e6e9a0e9698dc01950b6063363bf98 - languageName: node - linkType: hard - -"@opentelemetry/sdk-metrics@npm:2.0.1": - version: 2.0.1 - resolution: "@opentelemetry/sdk-metrics@npm:2.0.1" - dependencies: - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/resources": "npm:2.0.1" + "@opentelemetry/core": "npm:2.2.0" + "@opentelemetry/resources": "npm:2.2.0" peerDependencies: "@opentelemetry/api": ">=1.9.0 <1.10.0" - checksum: 10/eb23d0657ce7ef0784f6c89af650de83530099782758fce574316a8e82ff2bca0eb3adffa88c5fdd04eaced6150deb53ea0ea05aae06d2783795691734e85473 + checksum: 10/d6dacce73319e038d55a67f5b1a7a153531a703ef881b03df52f2d76685a4d53d0d840e02a0e0b24eddae4bd7d11c694e3c146f8db78e19d353316372d04c065 languageName: node linkType: hard -"@opentelemetry/sdk-node@npm:^0.202.0": - version: 0.202.0 - resolution: "@opentelemetry/sdk-node@npm:0.202.0" +"@opentelemetry/sdk-node@npm:^0.208.0": + version: 0.208.0 + resolution: "@opentelemetry/sdk-node@npm:0.208.0" dependencies: - "@opentelemetry/api-logs": "npm:0.202.0" - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/exporter-logs-otlp-grpc": "npm:0.202.0" - "@opentelemetry/exporter-logs-otlp-http": "npm:0.202.0" - "@opentelemetry/exporter-logs-otlp-proto": "npm:0.202.0" - "@opentelemetry/exporter-metrics-otlp-grpc": "npm:0.202.0" - "@opentelemetry/exporter-metrics-otlp-http": "npm:0.202.0" - "@opentelemetry/exporter-metrics-otlp-proto": "npm:0.202.0" - "@opentelemetry/exporter-prometheus": "npm:0.202.0" - "@opentelemetry/exporter-trace-otlp-grpc": "npm:0.202.0" - "@opentelemetry/exporter-trace-otlp-http": "npm:0.202.0" - "@opentelemetry/exporter-trace-otlp-proto": "npm:0.202.0" - "@opentelemetry/exporter-zipkin": "npm:2.0.1" - "@opentelemetry/instrumentation": "npm:0.202.0" - "@opentelemetry/propagator-b3": "npm:2.0.1" - "@opentelemetry/propagator-jaeger": "npm:2.0.1" - "@opentelemetry/resources": "npm:2.0.1" - "@opentelemetry/sdk-logs": "npm:0.202.0" - "@opentelemetry/sdk-metrics": "npm:2.0.1" - "@opentelemetry/sdk-trace-base": "npm:2.0.1" - "@opentelemetry/sdk-trace-node": "npm:2.0.1" + "@opentelemetry/api-logs": "npm:0.208.0" + "@opentelemetry/core": "npm:2.2.0" + "@opentelemetry/exporter-logs-otlp-grpc": "npm:0.208.0" + "@opentelemetry/exporter-logs-otlp-http": "npm:0.208.0" + "@opentelemetry/exporter-logs-otlp-proto": "npm:0.208.0" + "@opentelemetry/exporter-metrics-otlp-grpc": "npm:0.208.0" + "@opentelemetry/exporter-metrics-otlp-http": "npm:0.208.0" + "@opentelemetry/exporter-metrics-otlp-proto": "npm:0.208.0" + "@opentelemetry/exporter-prometheus": "npm:0.208.0" + "@opentelemetry/exporter-trace-otlp-grpc": "npm:0.208.0" + "@opentelemetry/exporter-trace-otlp-http": "npm:0.208.0" + "@opentelemetry/exporter-trace-otlp-proto": "npm:0.208.0" + "@opentelemetry/exporter-zipkin": "npm:2.2.0" + "@opentelemetry/instrumentation": "npm:0.208.0" + "@opentelemetry/propagator-b3": "npm:2.2.0" + "@opentelemetry/propagator-jaeger": "npm:2.2.0" + "@opentelemetry/resources": "npm:2.2.0" + "@opentelemetry/sdk-logs": "npm:0.208.0" + "@opentelemetry/sdk-metrics": "npm:2.2.0" + "@opentelemetry/sdk-trace-base": "npm:2.2.0" + "@opentelemetry/sdk-trace-node": "npm:2.2.0" "@opentelemetry/semantic-conventions": "npm:^1.29.0" peerDependencies: "@opentelemetry/api": ">=1.3.0 <1.10.0" - checksum: 10/80e3c6493bb5d87e5ce9c8b25dc5e2614c879b2ebe4f79f1a373d8237085b6e0bcaaff06acf01528e45849fb27e38cc0961fde6c87d7d2addf90cbcfbea43af7 + checksum: 10/929be561500feb30329314ee0f42adc064f0d9def9bf112b11c0f27ed3848366f172a13b178adf56fe96037f9e886c80a46122f105c2756a1fd1f4fdb0f831c2 languageName: node linkType: hard -"@opentelemetry/sdk-node@npm:^0.54.0": - version: 0.54.2 - resolution: "@opentelemetry/sdk-node@npm:0.54.2" +"@opentelemetry/sdk-trace-base@npm:2.2.0": + version: 2.2.0 + resolution: "@opentelemetry/sdk-trace-base@npm:2.2.0" dependencies: - "@opentelemetry/api-logs": "npm:0.54.2" - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/exporter-logs-otlp-grpc": "npm:0.54.2" - "@opentelemetry/exporter-logs-otlp-http": "npm:0.54.2" - "@opentelemetry/exporter-logs-otlp-proto": "npm:0.54.2" - "@opentelemetry/exporter-trace-otlp-grpc": "npm:0.54.2" - "@opentelemetry/exporter-trace-otlp-http": "npm:0.54.2" - "@opentelemetry/exporter-trace-otlp-proto": "npm:0.54.2" - "@opentelemetry/exporter-zipkin": "npm:1.27.0" - "@opentelemetry/instrumentation": "npm:0.54.2" - "@opentelemetry/resources": "npm:1.27.0" - "@opentelemetry/sdk-logs": "npm:0.54.2" - "@opentelemetry/sdk-metrics": "npm:1.27.0" - "@opentelemetry/sdk-trace-base": "npm:1.27.0" - "@opentelemetry/sdk-trace-node": "npm:1.27.0" - "@opentelemetry/semantic-conventions": "npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ">=1.3.0 <1.10.0" - checksum: 10/36de50763eb13ce720ab50670870562f9d12583d9b18d0d7c9dce8b8d5fa08bee1d0c12e8a3b17b5208e934e65091ab2f21f1828a593ed431d6dde51429b28a3 - languageName: node - linkType: hard - -"@opentelemetry/sdk-trace-base@npm:1.27.0": - version: 1.27.0 - resolution: "@opentelemetry/sdk-trace-base@npm:1.27.0" - dependencies: - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/resources": "npm:1.27.0" - "@opentelemetry/semantic-conventions": "npm:1.27.0" - peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/e0023dedbf5a50265729dd5467be0504f04f6b43d4cc4b914a9a8c082cca1aec9250a1f31c615f3d4ad03124af6e0ba1e14b39fe745d14291198f2a0990edc9c - languageName: node - linkType: hard - -"@opentelemetry/sdk-trace-base@npm:2.0.1": - version: 2.0.1 - resolution: "@opentelemetry/sdk-trace-base@npm:2.0.1" - dependencies: - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/resources": "npm:2.0.1" + "@opentelemetry/core": "npm:2.2.0" + "@opentelemetry/resources": "npm:2.2.0" "@opentelemetry/semantic-conventions": "npm:^1.29.0" peerDependencies: "@opentelemetry/api": ">=1.3.0 <1.10.0" - checksum: 10/9de1e36bbce9bd7c0563e6395765fffc0f8c78806cb33cc95267e98dffd82de33857a51288073a104c10418b934e51560bcb5dcaf4e63e5c9e096f65cadd42cd + checksum: 10/0838128f965055b5f8d37026a2f4736ebb77a772a94b9f5b7accb0447a44cfa279da4da959a82565e958e1676ad2a02c17f6fd0e688b205bdde0c846e310c643 languageName: node linkType: hard -"@opentelemetry/sdk-trace-node@npm:1.27.0": - version: 1.27.0 - resolution: "@opentelemetry/sdk-trace-node@npm:1.27.0" +"@opentelemetry/sdk-trace-node@npm:2.2.0": + version: 2.2.0 + resolution: "@opentelemetry/sdk-trace-node@npm:2.2.0" dependencies: - "@opentelemetry/context-async-hooks": "npm:1.27.0" - "@opentelemetry/core": "npm:1.27.0" - "@opentelemetry/propagator-b3": "npm:1.27.0" - "@opentelemetry/propagator-jaeger": "npm:1.27.0" - "@opentelemetry/sdk-trace-base": "npm:1.27.0" - semver: "npm:^7.5.2" + "@opentelemetry/context-async-hooks": "npm:2.2.0" + "@opentelemetry/core": "npm:2.2.0" + "@opentelemetry/sdk-trace-base": "npm:2.2.0" peerDependencies: "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/53cff496312f0dedd819d2998219596a0d700348bf8ad315ff1d48a9a5e9be070623ed850633436f4ad006263b9cb55ffb6681d4174de99547d4edaa4125c98d - languageName: node - linkType: hard - -"@opentelemetry/sdk-trace-node@npm:2.0.1": - version: 2.0.1 - resolution: "@opentelemetry/sdk-trace-node@npm:2.0.1" - dependencies: - "@opentelemetry/context-async-hooks": "npm:2.0.1" - "@opentelemetry/core": "npm:2.0.1" - "@opentelemetry/sdk-trace-base": "npm:2.0.1" - peerDependencies: - "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/7c5b4a902ba88ef03f02da83f6900080d81bb3264752419981dc44faabfe2bf4dbe91d25fec4a671afd5212151eee535a5205137772a8fd2c8d247950eb6556f - languageName: node - linkType: hard - -"@opentelemetry/semantic-conventions@npm:1.27.0": - version: 1.27.0 - resolution: "@opentelemetry/semantic-conventions@npm:1.27.0" - checksum: 10/98166522f299e2fe3d43376adbdeb92679b75ebb172e2a3c4c71f2942bd91585e9537618efbbae6dc08177699e5719368edf66d7e69e8636f360b85217bbdbe1 + checksum: 10/4918bc6a66649203d7165a8beca5a3ebaf05d91a2b4910aa195f582a6b50658e86303e00a4fc53172e48cac3cc79b50906d70a3b989895b423dc90dad5e9b5da languageName: node linkType: hard @@ -14080,10 +14020,10 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/semantic-conventions@npm:^1.27.0, @opentelemetry/semantic-conventions@npm:^1.29.0, @opentelemetry/semantic-conventions@npm:^1.30.0, @opentelemetry/semantic-conventions@npm:^1.33.1, @opentelemetry/semantic-conventions@npm:^1.34.0": - version: 1.34.0 - resolution: "@opentelemetry/semantic-conventions@npm:1.34.0" - checksum: 10/1892b4cc69c9e00456c809604a980e32696563e96463ff5f9d07e72d5aca73836a7378090509f28f54445ac6e072d2343a888c9d64d9ce287198e899082ff7aa +"@opentelemetry/semantic-conventions@npm:^1.24.0, @opentelemetry/semantic-conventions@npm:^1.27.0, @opentelemetry/semantic-conventions@npm:^1.29.0, @opentelemetry/semantic-conventions@npm:^1.30.0, @opentelemetry/semantic-conventions@npm:^1.33.0, @opentelemetry/semantic-conventions@npm:^1.33.1, @opentelemetry/semantic-conventions@npm:^1.34.0, @opentelemetry/semantic-conventions@npm:^1.36.0, @opentelemetry/semantic-conventions@npm:^1.37.0": + version: 1.38.0 + resolution: "@opentelemetry/semantic-conventions@npm:1.38.0" + checksum: 10/9d549f4896e900f644d5e70dd7142505daff88ed83c1cb7bcd976ac55e9496d4ddd686bb2815dd68655c739950514394c3b73ff51e53b2e4ff2d54a7f6d22521 languageName: node linkType: hard @@ -14094,14 +14034,156 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/sql-common@npm:^0.41.0": - version: 0.41.0 - resolution: "@opentelemetry/sql-common@npm:0.41.0" +"@opentelemetry/sql-common@npm:^0.41.2": + version: 0.41.2 + resolution: "@opentelemetry/sql-common@npm:0.41.2" dependencies: "@opentelemetry/core": "npm:^2.0.0" peerDependencies: "@opentelemetry/api": ^1.1.0 - checksum: 10/182915f050b8b685f5499e9fa4ed9a9730fcd404889e5066cfc7d94e9961b1f2780216d776070a4a5c1b94323c287ab426b06f5a8828d5fea3daf664bfd397bb + checksum: 10/3d57d5162c69c29484cb166e99ac733fff1dcefa26aea401e40035d5daa3aef21af78936af7943d0dfdab385ff053b879117c2fa3bd980412dd378222b10bea3 + languageName: node + linkType: hard + +"@oxc-resolver/binding-android-arm-eabi@npm:11.14.2": + version: 11.14.2 + resolution: "@oxc-resolver/binding-android-arm-eabi@npm:11.14.2" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@oxc-resolver/binding-android-arm64@npm:11.14.2": + version: 11.14.2 + resolution: "@oxc-resolver/binding-android-arm64@npm:11.14.2" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@oxc-resolver/binding-darwin-arm64@npm:11.14.2": + version: 11.14.2 + resolution: "@oxc-resolver/binding-darwin-arm64@npm:11.14.2" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@oxc-resolver/binding-darwin-x64@npm:11.14.2": + version: 11.14.2 + resolution: "@oxc-resolver/binding-darwin-x64@npm:11.14.2" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@oxc-resolver/binding-freebsd-x64@npm:11.14.2": + version: 11.14.2 + resolution: "@oxc-resolver/binding-freebsd-x64@npm:11.14.2" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-arm-gnueabihf@npm:11.14.2": + version: 11.14.2 + resolution: "@oxc-resolver/binding-linux-arm-gnueabihf@npm:11.14.2" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-arm-musleabihf@npm:11.14.2": + version: 11.14.2 + resolution: "@oxc-resolver/binding-linux-arm-musleabihf@npm:11.14.2" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-arm64-gnu@npm:11.14.2": + version: 11.14.2 + resolution: "@oxc-resolver/binding-linux-arm64-gnu@npm:11.14.2" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-arm64-musl@npm:11.14.2": + version: 11.14.2 + resolution: "@oxc-resolver/binding-linux-arm64-musl@npm:11.14.2" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-ppc64-gnu@npm:11.14.2": + version: 11.14.2 + resolution: "@oxc-resolver/binding-linux-ppc64-gnu@npm:11.14.2" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-riscv64-gnu@npm:11.14.2": + version: 11.14.2 + resolution: "@oxc-resolver/binding-linux-riscv64-gnu@npm:11.14.2" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-riscv64-musl@npm:11.14.2": + version: 11.14.2 + resolution: "@oxc-resolver/binding-linux-riscv64-musl@npm:11.14.2" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-s390x-gnu@npm:11.14.2": + version: 11.14.2 + resolution: "@oxc-resolver/binding-linux-s390x-gnu@npm:11.14.2" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-x64-gnu@npm:11.14.2": + version: 11.14.2 + resolution: "@oxc-resolver/binding-linux-x64-gnu@npm:11.14.2" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-x64-musl@npm:11.14.2": + version: 11.14.2 + resolution: "@oxc-resolver/binding-linux-x64-musl@npm:11.14.2" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@oxc-resolver/binding-openharmony-arm64@npm:11.14.2": + version: 11.14.2 + resolution: "@oxc-resolver/binding-openharmony-arm64@npm:11.14.2" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@oxc-resolver/binding-wasm32-wasi@npm:11.14.2": + version: 11.14.2 + resolution: "@oxc-resolver/binding-wasm32-wasi@npm:11.14.2" + dependencies: + "@napi-rs/wasm-runtime": "npm:^1.1.0" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@oxc-resolver/binding-win32-arm64-msvc@npm:11.14.2": + version: 11.14.2 + resolution: "@oxc-resolver/binding-win32-arm64-msvc@npm:11.14.2" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@oxc-resolver/binding-win32-ia32-msvc@npm:11.14.2": + version: 11.14.2 + resolution: "@oxc-resolver/binding-win32-ia32-msvc@npm:11.14.2" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@oxc-resolver/binding-win32-x64-msvc@npm:11.14.2": + version: 11.14.2 + resolution: "@oxc-resolver/binding-win32-x64-msvc@npm:11.14.2" + conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -14121,37 +14203,43 @@ __metadata: languageName: node linkType: hard -"@playwright/test@npm:^1.32.3": - version: 1.56.1 - resolution: "@playwright/test@npm:1.56.1" - dependencies: - playwright: "npm:1.56.1" - bin: - playwright: cli.js - checksum: 10/9933fa9f8eb9e775e792421b99c984c310b92092e65de57508ae1951a2589d87bbb5f1c4114bfdf7f69c15c0a3acb3f31259e143fa597aa28e97f4b223e37637 +"@pkgr/core@npm:^0.2.9": + version: 0.2.9 + resolution: "@pkgr/core@npm:0.2.9" + checksum: 10/bb2fb86977d63f836f8f5b09015d74e6af6488f7a411dcd2bfdca79d76b5a681a9112f41c45bdf88a9069f049718efc6f3900d7f1de66a2ec966068308ae517f languageName: node linkType: hard -"@pmmmwh/react-refresh-webpack-plugin@npm:^0.5.7": - version: 0.5.16 - resolution: "@pmmmwh/react-refresh-webpack-plugin@npm:0.5.16" +"@playwright/test@npm:^1.32.3": + version: 1.57.0 + resolution: "@playwright/test@npm:1.57.0" dependencies: - ansi-html: "npm:^0.0.9" + playwright: "npm:1.57.0" + bin: + playwright: cli.js + checksum: 10/07f5ba4841b2db1dea70d821004c5156b692488e13523c096ce3487d30f95f34ccf30ba6467ece60c86faac27ae382213b7eacab48a695550981b2e811e5e579 + languageName: node + linkType: hard + +"@pmmmwh/react-refresh-webpack-plugin@npm:^0.6.0": + version: 0.6.2 + resolution: "@pmmmwh/react-refresh-webpack-plugin@npm:0.6.2" + dependencies: + anser: "npm:^2.1.1" core-js-pure: "npm:^3.23.3" error-stack-parser: "npm:^2.0.6" html-entities: "npm:^2.1.0" - loader-utils: "npm:^2.0.4" schema-utils: "npm:^4.2.0" source-map: "npm:^0.7.3" peerDependencies: - "@types/webpack": 4.x || 5.x + "@types/webpack": 5.x react-refresh: ">=0.10.0 <1.0.0" sockjs-client: ^1.4.0 - type-fest: ">=0.17.0 <5.0.0" - webpack: ">=4.43.0 <6.0.0" - webpack-dev-server: 3.x || 4.x || 5.x + type-fest: ">=0.17.0 <6.0.0" + webpack: ^5.0.0 + webpack-dev-server: ^4.8.0 || 5.x webpack-hot-middleware: 2.x - webpack-plugin-serve: 0.x || 1.x + webpack-plugin-serve: 1.x peerDependenciesMeta: "@types/webpack": optional: true @@ -14165,7 +14253,7 @@ __metadata: optional: true webpack-plugin-serve: optional: true - checksum: 10/d7ac4c58a377d37a4dc2f19d1434a14f83ff73d38fd7e374bdab2b2b73b7dc398803c1fb6eb84e8787417bffdc1735f83f65f55d8acac30a800de7122c16428e + checksum: 10/157a20464b0bdca39b31e09450f6ce1d91cbe32bcce882a02df797482b0226247d13e5a2c750a3fb2e2758bffc25d49a468b33b66d8364aa4e1ed785596453c8 languageName: node linkType: hard @@ -16573,38 +16661,37 @@ __metadata: languageName: node linkType: hard -"@rjsf/core@npm:5.23.2": - version: 5.23.2 - resolution: "@rjsf/core@npm:5.23.2" +"@rjsf/core@npm:5.24.13": + version: 5.24.13 + resolution: "@rjsf/core@npm:5.24.13" dependencies: lodash: "npm:^4.17.21" lodash-es: "npm:^4.17.21" markdown-to-jsx: "npm:^7.4.1" - nanoid: "npm:^3.3.7" prop-types: "npm:^15.8.1" peerDependencies: - "@rjsf/utils": ^5.23.x + "@rjsf/utils": ^5.24.x react: ^16.14.0 || >=17 - checksum: 10/a49cbd41bb8b499ad2ed521417f53bf3fa65c09bedbf0e02ce0f00a5287a3ae4df774e74f89a8dcb8a24359d382a585ea4d20c3a6e109f4aaad2cd74b7db2b01 + checksum: 10/80defb9ccbb563722dc0a613358122e86c775d10af04d1298e416e7069fa30cddf71823c82444fb1b5ab2f8e4706ff09f2606259fdd8ea59865373eb9493eb5e languageName: node linkType: hard -"@rjsf/material-ui@npm:5.23.2": - version: 5.23.2 - resolution: "@rjsf/material-ui@npm:5.23.2" +"@rjsf/material-ui@npm:5.24.13": + version: 5.24.13 + resolution: "@rjsf/material-ui@npm:5.24.13" peerDependencies: "@material-ui/core": ^4.12.3 "@material-ui/icons": ^4.11.2 - "@rjsf/core": ^5.23.x - "@rjsf/utils": ^5.23.x + "@rjsf/core": ^5.24.x + "@rjsf/utils": ^5.24.x react: ^16.14.0 || >=17 - checksum: 10/0c9ab33d4a2251bc4a3868fd09e6267ccd23e96b226204e1e2b5d9667fec375f64ac9674f3f5c8b013bb6e0b745f31854cab92b859b0c9ff2527338760664d5f + checksum: 10/b52e35973e81670cf57d8d3bda10b2b1bc57dfb5b6ab7be6176e6d5c384d40b86171bb30e7712e0d5971428e1553a70de08935fa680ef6877b0e97ad4c192059 languageName: node linkType: hard -"@rjsf/utils@npm:5.23.2": - version: 5.23.2 - resolution: "@rjsf/utils@npm:5.23.2" +"@rjsf/utils@npm:5.24.13": + version: 5.24.13 + resolution: "@rjsf/utils@npm:5.24.13" dependencies: json-schema-merge-allof: "npm:^0.8.1" jsonpointer: "npm:^5.0.1" @@ -16613,21 +16700,21 @@ __metadata: react-is: "npm:^18.2.0" peerDependencies: react: ^16.14.0 || >=17 - checksum: 10/739a65a40dede96dd1d202ad0c0df96ffe4c75cd3ebcf035da9589eecf2875100c9e28066e135ececa4c188abee436b9ccaa1536992ab5d8417bb6c9e43bd156 + checksum: 10/4bd6788c4d12c147bd26c67568267ce34d15f618c1f38f964e6981d202bf78e2101ab0d286987659840455115bd8f183aa133308bf0f8468ca44153723e3c6d0 languageName: node linkType: hard -"@rjsf/validator-ajv8@npm:5.23.2": - version: 5.23.2 - resolution: "@rjsf/validator-ajv8@npm:5.23.2" +"@rjsf/validator-ajv8@npm:5.24.13": + version: 5.24.13 + resolution: "@rjsf/validator-ajv8@npm:5.24.13" dependencies: ajv: "npm:^8.12.0" ajv-formats: "npm:^2.1.1" lodash: "npm:^4.17.21" lodash-es: "npm:^4.17.21" peerDependencies: - "@rjsf/utils": ^5.23.x - checksum: 10/568693ef0b93f21000b3b9352a65dd65001b4b85514fd94a1bf7562dc9ab8333f3a077c5335041fd7d250fac9a6c9cb77ea8539d8589a03afa55539898f896e6 + "@rjsf/utils": ^5.24.x + checksum: 10/a684862ded27792c8f40cfcf26436fa91f0666c6b95aa7281bb6448221a85cffb6a178fd99d172758aae2c41c58033047d902743962b7f3a2b46204ebdb0009d languageName: node linkType: hard @@ -16698,7 +16785,7 @@ __metadata: languageName: node linkType: hard -"@rollup/pluginutils@npm:^5.0.1, @rollup/pluginutils@npm:^5.0.2, @rollup/pluginutils@npm:^5.0.5, @rollup/pluginutils@npm:^5.1.0": +"@rollup/pluginutils@npm:^5.0.1, @rollup/pluginutils@npm:^5.0.2, @rollup/pluginutils@npm:^5.1.0": version: 5.2.0 resolution: "@rollup/pluginutils@npm:5.2.0" dependencies: @@ -16714,232 +16801,246 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-android-arm-eabi@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.46.2" +"@rollup/rollup-android-arm-eabi@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.53.3" conditions: os=android & cpu=arm languageName: node linkType: hard -"@rollup/rollup-android-arm64@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-android-arm64@npm:4.46.2" +"@rollup/rollup-android-arm64@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-android-arm64@npm:4.53.3" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-arm64@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-darwin-arm64@npm:4.46.2" +"@rollup/rollup-darwin-arm64@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-darwin-arm64@npm:4.53.3" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-x64@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-darwin-x64@npm:4.46.2" +"@rollup/rollup-darwin-x64@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-darwin-x64@npm:4.53.3" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-freebsd-arm64@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-freebsd-arm64@npm:4.46.2" +"@rollup/rollup-freebsd-arm64@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.53.3" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-freebsd-x64@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-freebsd-x64@npm:4.46.2" +"@rollup/rollup-freebsd-x64@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-freebsd-x64@npm:4.53.3" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-linux-arm-gnueabihf@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.46.2" +"@rollup/rollup-linux-arm-gnueabihf@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.53.3" conditions: os=linux & cpu=arm & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm-musleabihf@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.46.2" +"@rollup/rollup-linux-arm-musleabihf@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.53.3" conditions: os=linux & cpu=arm & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-arm64-gnu@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.46.2" +"@rollup/rollup-linux-arm64-gnu@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.53.3" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm64-musl@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.46.2" +"@rollup/rollup-linux-arm64-musl@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.53.3" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-loongarch64-gnu@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.46.2" +"@rollup/rollup-linux-loong64-gnu@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.53.3" conditions: os=linux & cpu=loong64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-ppc64-gnu@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.46.2" +"@rollup/rollup-linux-ppc64-gnu@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.53.3" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-gnu@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.46.2" +"@rollup/rollup-linux-riscv64-gnu@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.53.3" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-musl@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.46.2" +"@rollup/rollup-linux-riscv64-musl@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.53.3" conditions: os=linux & cpu=riscv64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-s390x-gnu@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.46.2" +"@rollup/rollup-linux-s390x-gnu@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.53.3" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-gnu@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.46.2" +"@rollup/rollup-linux-x64-gnu@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.53.3" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-musl@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.46.2" +"@rollup/rollup-linux-x64-musl@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.53.3" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-win32-arm64-msvc@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.46.2" +"@rollup/rollup-openharmony-arm64@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-openharmony-arm64@npm:4.53.3" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-arm64-msvc@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.53.3" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-ia32-msvc@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.46.2" +"@rollup/rollup-win32-ia32-msvc@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.53.3" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@rollup/rollup-win32-x64-msvc@npm:4.46.2": - version: 4.46.2 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.46.2" +"@rollup/rollup-win32-x64-gnu@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-win32-x64-gnu@npm:4.53.3" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@rspack/binding-darwin-arm64@npm:1.6.3": - version: 1.6.3 - resolution: "@rspack/binding-darwin-arm64@npm:1.6.3" +"@rollup/rollup-win32-x64-msvc@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.53.3" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@rspack/binding-darwin-arm64@npm:1.6.7": + version: 1.6.7 + resolution: "@rspack/binding-darwin-arm64@npm:1.6.7" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rspack/binding-darwin-x64@npm:1.6.3": - version: 1.6.3 - resolution: "@rspack/binding-darwin-x64@npm:1.6.3" +"@rspack/binding-darwin-x64@npm:1.6.7": + version: 1.6.7 + resolution: "@rspack/binding-darwin-x64@npm:1.6.7" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rspack/binding-linux-arm64-gnu@npm:1.6.3": - version: 1.6.3 - resolution: "@rspack/binding-linux-arm64-gnu@npm:1.6.3" +"@rspack/binding-linux-arm64-gnu@npm:1.6.7": + version: 1.6.7 + resolution: "@rspack/binding-linux-arm64-gnu@npm:1.6.7" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rspack/binding-linux-arm64-musl@npm:1.6.3": - version: 1.6.3 - resolution: "@rspack/binding-linux-arm64-musl@npm:1.6.3" +"@rspack/binding-linux-arm64-musl@npm:1.6.7": + version: 1.6.7 + resolution: "@rspack/binding-linux-arm64-musl@npm:1.6.7" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rspack/binding-linux-x64-gnu@npm:1.6.3": - version: 1.6.3 - resolution: "@rspack/binding-linux-x64-gnu@npm:1.6.3" +"@rspack/binding-linux-x64-gnu@npm:1.6.7": + version: 1.6.7 + resolution: "@rspack/binding-linux-x64-gnu@npm:1.6.7" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rspack/binding-linux-x64-musl@npm:1.6.3": - version: 1.6.3 - resolution: "@rspack/binding-linux-x64-musl@npm:1.6.3" +"@rspack/binding-linux-x64-musl@npm:1.6.7": + version: 1.6.7 + resolution: "@rspack/binding-linux-x64-musl@npm:1.6.7" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rspack/binding-wasm32-wasi@npm:1.6.3": - version: 1.6.3 - resolution: "@rspack/binding-wasm32-wasi@npm:1.6.3" +"@rspack/binding-wasm32-wasi@npm:1.6.7": + version: 1.6.7 + resolution: "@rspack/binding-wasm32-wasi@npm:1.6.7" dependencies: "@napi-rs/wasm-runtime": "npm:1.0.7" conditions: cpu=wasm32 languageName: node linkType: hard -"@rspack/binding-win32-arm64-msvc@npm:1.6.3": - version: 1.6.3 - resolution: "@rspack/binding-win32-arm64-msvc@npm:1.6.3" +"@rspack/binding-win32-arm64-msvc@npm:1.6.7": + version: 1.6.7 + resolution: "@rspack/binding-win32-arm64-msvc@npm:1.6.7" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rspack/binding-win32-ia32-msvc@npm:1.6.3": - version: 1.6.3 - resolution: "@rspack/binding-win32-ia32-msvc@npm:1.6.3" +"@rspack/binding-win32-ia32-msvc@npm:1.6.7": + version: 1.6.7 + resolution: "@rspack/binding-win32-ia32-msvc@npm:1.6.7" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@rspack/binding-win32-x64-msvc@npm:1.6.3": - version: 1.6.3 - resolution: "@rspack/binding-win32-x64-msvc@npm:1.6.3" +"@rspack/binding-win32-x64-msvc@npm:1.6.7": + version: 1.6.7 + resolution: "@rspack/binding-win32-x64-msvc@npm:1.6.7" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@rspack/binding@npm:1.6.3": - version: 1.6.3 - resolution: "@rspack/binding@npm:1.6.3" +"@rspack/binding@npm:1.6.7": + version: 1.6.7 + resolution: "@rspack/binding@npm:1.6.7" dependencies: - "@rspack/binding-darwin-arm64": "npm:1.6.3" - "@rspack/binding-darwin-x64": "npm:1.6.3" - "@rspack/binding-linux-arm64-gnu": "npm:1.6.3" - "@rspack/binding-linux-arm64-musl": "npm:1.6.3" - "@rspack/binding-linux-x64-gnu": "npm:1.6.3" - "@rspack/binding-linux-x64-musl": "npm:1.6.3" - "@rspack/binding-wasm32-wasi": "npm:1.6.3" - "@rspack/binding-win32-arm64-msvc": "npm:1.6.3" - "@rspack/binding-win32-ia32-msvc": "npm:1.6.3" - "@rspack/binding-win32-x64-msvc": "npm:1.6.3" + "@rspack/binding-darwin-arm64": "npm:1.6.7" + "@rspack/binding-darwin-x64": "npm:1.6.7" + "@rspack/binding-linux-arm64-gnu": "npm:1.6.7" + "@rspack/binding-linux-arm64-musl": "npm:1.6.7" + "@rspack/binding-linux-x64-gnu": "npm:1.6.7" + "@rspack/binding-linux-x64-musl": "npm:1.6.7" + "@rspack/binding-wasm32-wasi": "npm:1.6.7" + "@rspack/binding-win32-arm64-msvc": "npm:1.6.7" + "@rspack/binding-win32-ia32-msvc": "npm:1.6.7" + "@rspack/binding-win32-x64-msvc": "npm:1.6.7" dependenciesMeta: "@rspack/binding-darwin-arm64": optional: true @@ -16961,23 +17062,23 @@ __metadata: optional: true "@rspack/binding-win32-x64-msvc": optional: true - checksum: 10/c2ae95df065a3fc00fe81b8aef72dc1a4b80db274656e0c03db82fd12fba2d3a40514a741e0c0d3422d896d49eb34863d81b5c23d29df6d5f692e5b15a916510 + checksum: 10/80837f5cbdcac9eb6fb2213436baa3a5a841508340456c473e36bed2882197f456462db675b2d43cc1ed84d61109bae4bef0d474131a670113601ad80727769f languageName: node linkType: hard "@rspack/core@npm:^1.4.11": - version: 1.6.3 - resolution: "@rspack/core@npm:1.6.3" + version: 1.6.7 + resolution: "@rspack/core@npm:1.6.7" dependencies: - "@module-federation/runtime-tools": "npm:0.21.4" - "@rspack/binding": "npm:1.6.3" + "@module-federation/runtime-tools": "npm:0.21.6" + "@rspack/binding": "npm:1.6.7" "@rspack/lite-tapable": "npm:1.1.0" peerDependencies: "@swc/helpers": ">=0.5.1" peerDependenciesMeta: "@swc/helpers": optional: true - checksum: 10/01bb10cf18331b2d6af841bc33bd15c1a01f01c5b16fd9b598e6b013ff138f91a9124c39f85530fd2a66dd693d0fa1252fc0331f0be9d7f2452c315c0a6e619a + checksum: 10/c374d954c86a24dfba59b6e3c6388ce365723c83ea7012a6e95175b6245f7080b13e8560c25d744558e850c04a10d95fbe7ee92b4eabbd40a1ef578caf60615b languageName: node linkType: hard @@ -16996,7 +17097,7 @@ __metadata: languageName: node linkType: hard -"@rspack/lite-tapable@npm:1.1.0, @rspack/lite-tapable@npm:^1.0.1": +"@rspack/lite-tapable@npm:1.1.0, @rspack/lite-tapable@npm:^1.1.0": version: 1.1.0 resolution: "@rspack/lite-tapable@npm:1.1.0" checksum: 10/41ff73fe5e1b8dccaad746c9c1bd36dd67649e1ad35776f311b5ba94333a397704e11158579e25a6a7e677c51abe35e66987b1b000faef48d4e4ad2470fea150 @@ -17026,14 +17127,14 @@ __metadata: languageName: node linkType: hard -"@rushstack/node-core-library@npm:5.9.0": - version: 5.9.0 - resolution: "@rushstack/node-core-library@npm:5.9.0" +"@rushstack/node-core-library@npm:5.19.1": + version: 5.19.1 + resolution: "@rushstack/node-core-library@npm:5.19.1" dependencies: ajv: "npm:~8.13.0" ajv-draft-04: "npm:~1.0.0" ajv-formats: "npm:~3.0.1" - fs-extra: "npm:~7.0.1" + fs-extra: "npm:~11.3.0" import-lazy: "npm:~4.0.0" jju: "npm:~1.4.0" resolve: "npm:~1.22.1" @@ -17043,44 +17144,57 @@ __metadata: peerDependenciesMeta: "@types/node": optional: true - checksum: 10/19d6c6fc6addfb27295d1d78e1027f5896ea43702c2e5168e8b235ee739f2e59a4dc3bb3996b083722fab4feae5f2c15c340900c6c1d517d56cc1ba945f532f1 + checksum: 10/a674c4ed4cf3c863ab6bfff0e3615e7f791d0312fa5942027b6e8070b4453464c0e77741a1ed13bc01fab66ddc89e8c068c6c58ce7d81c014966628b75223bd6 languageName: node linkType: hard -"@rushstack/rig-package@npm:0.5.3": - version: 0.5.3 - resolution: "@rushstack/rig-package@npm:0.5.3" +"@rushstack/problem-matcher@npm:0.1.1": + version: 0.1.1 + resolution: "@rushstack/problem-matcher@npm:0.1.1" + peerDependencies: + "@types/node": "*" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10/a47c2d5fd0e3bbe7336f06c29ef91061e36ab8dafd04c861392806e60a3366fd8c3921be217adc71d039c8749f6b70a06c874ff314501eed5b7f8fb7b42c7a39 + languageName: node + linkType: hard + +"@rushstack/rig-package@npm:0.6.0": + version: 0.6.0 + resolution: "@rushstack/rig-package@npm:0.6.0" dependencies: resolve: "npm:~1.22.1" strip-json-comments: "npm:~3.1.1" - checksum: 10/b58a3925a41d7a0e79f4fde7c400a379683cc7b0073c447aba6d36231529a37e7d2f4559f459be785ad862ecb01b618b2d0ff60661046e5223437356155ccb14 + checksum: 10/6ca5d6615365dfe4d78fdc52a1a145bec92bba79d8692db91d05c774b4ec4d9dc6c41b31949708d0312896b9c1c205a0f0eaa32f51ac7b1780415ac51c76af71 languageName: node linkType: hard -"@rushstack/terminal@npm:0.14.2": - version: 0.14.2 - resolution: "@rushstack/terminal@npm:0.14.2" +"@rushstack/terminal@npm:0.19.5": + version: 0.19.5 + resolution: "@rushstack/terminal@npm:0.19.5" dependencies: - "@rushstack/node-core-library": "npm:5.9.0" + "@rushstack/node-core-library": "npm:5.19.1" + "@rushstack/problem-matcher": "npm:0.1.1" supports-color: "npm:~8.1.1" peerDependencies: "@types/node": "*" peerDependenciesMeta: "@types/node": optional: true - checksum: 10/4016499f3ed1eff7d870ff029bc51925f1435c0ed73d454385d6fdfe8f10e7e5fc2dba698a9aa671f2537603d2d08449782a0f329f321a7dcb87827c19385bca + checksum: 10/c5118df78045153aaf430de66e1d79befc729fd1f5df5dc8eef3dff1eb8f75a277f0dc8bad344b313d0485ace69400b1aaf69ad061c601a7bd8291ae5784b6a3 languageName: node linkType: hard -"@rushstack/ts-command-line@npm:4.22.8": - version: 4.22.8 - resolution: "@rushstack/ts-command-line@npm:4.22.8" +"@rushstack/ts-command-line@npm:5.1.5": + version: 5.1.5 + resolution: "@rushstack/ts-command-line@npm:5.1.5" dependencies: - "@rushstack/terminal": "npm:0.14.2" + "@rushstack/terminal": "npm:0.19.5" "@types/argparse": "npm:1.0.38" argparse: "npm:~1.0.9" string-argv: "npm:~0.3.1" - checksum: 10/f4d57a50b320f382929471258aaab6f38251a4febac981a503ae5217faec6fff68374d0126af59554d2f4a99e0ac67596649109fcc57608ef144b132de83cb63 + checksum: 10/4e3bc090b9c40ec89729aa96d45b08aeac725ce3fda9951d3b87b485fe48299db2dfa7a338acf7e89f380a5b98d44df8b4a18067962fecfb8ba64a8919e6dd2b languageName: node linkType: hard @@ -17212,41 +17326,41 @@ __metadata: languageName: node linkType: hard -"@shikijs/engine-oniguruma@npm:^3.14.0": - version: 3.14.0 - resolution: "@shikijs/engine-oniguruma@npm:3.14.0" +"@shikijs/engine-oniguruma@npm:^3.17.1": + version: 3.18.0 + resolution: "@shikijs/engine-oniguruma@npm:3.18.0" dependencies: - "@shikijs/types": "npm:3.14.0" + "@shikijs/types": "npm:3.18.0" "@shikijs/vscode-textmate": "npm:^10.0.2" - checksum: 10/54fd65a8d4c2d85c170f324b122025b7c8f5f7d2e856e918ba8fed47efbca6ec976e92b37e463c8007527f597eec5403fcafad480b24588fc3304dcc66d06938 + checksum: 10/66fddeedd5e96b926ed06081dc678738b599c47da80e2f8809bd812398e9ba6e0fb00b7943a92e21c22c7aea2a813042bd936d28ee25901c58182e023122a176 languageName: node linkType: hard -"@shikijs/langs@npm:^3.14.0": - version: 3.14.0 - resolution: "@shikijs/langs@npm:3.14.0" +"@shikijs/langs@npm:^3.17.1": + version: 3.18.0 + resolution: "@shikijs/langs@npm:3.18.0" dependencies: - "@shikijs/types": "npm:3.14.0" - checksum: 10/fcbf350ab743dec154e0fedae40112d732930f8bf7e91829e492625c9e49067b7ee684ea276550894dcaea72ed0df63e97872cf7d2ed64fb6ab9f0cbc2431ce9 + "@shikijs/types": "npm:3.18.0" + checksum: 10/6d872ea7d700081f4c021bea0692948443cb47f5664d5977ad902da953bdfaf71a2a5fba0e6d159ad279a37ce62fdb473698922495813c5dd2de2ecd8b3683c4 languageName: node linkType: hard -"@shikijs/themes@npm:^3.14.0": - version: 3.14.0 - resolution: "@shikijs/themes@npm:3.14.0" +"@shikijs/themes@npm:^3.17.1": + version: 3.18.0 + resolution: "@shikijs/themes@npm:3.18.0" dependencies: - "@shikijs/types": "npm:3.14.0" - checksum: 10/4ab89e6eaa1de0b41ab0fb3fefa65684cae87c6efab6cd75d951f3fee81faba04f022cdc45763956cc2415d08cf56ab2c3477655570ef8ef0a4ab0c0affe9190 + "@shikijs/types": "npm:3.18.0" + checksum: 10/6b5221a23042fd4ce2af60dc36f85b04615f962990cc8a1c6e8b0ca12c6af2fb7152b5c2e89213236436a46751cc746c98fbb7ca72ef9f1a950cf3c4ec10277a languageName: node linkType: hard -"@shikijs/types@npm:3.14.0, @shikijs/types@npm:^3.14.0": - version: 3.14.0 - resolution: "@shikijs/types@npm:3.14.0" +"@shikijs/types@npm:3.18.0, @shikijs/types@npm:^3.17.1": + version: 3.18.0 + resolution: "@shikijs/types@npm:3.18.0" dependencies: "@shikijs/vscode-textmate": "npm:^10.0.2" "@types/hast": "npm:^3.0.4" - checksum: 10/361e6cd13a7c32f73ee056281fca19e53214450663ddf245c87f06e22c9d21e102cc5b8ef0778f0bf85146387b45347ae2f504591f5fd192f8ef3c5e2fcfccda + checksum: 10/32a11448e716a31a6ef24483f6063fcb43243e6801fed235af25a7afc5ca23f9115616338e3cec3b64f6ec8bf6aa674571344636931331948da012269792a47d languageName: node linkType: hard @@ -17340,6 +17454,13 @@ __metadata: languageName: node linkType: hard +"@sinclair/typebox@npm:^0.34.0": + version: 0.34.41 + resolution: "@sinclair/typebox@npm:0.34.41" + checksum: 10/5c04a7f42156a7813a159947a0c3fe7e9f11aa722141ac3ff32242faf031b443ef71763d8791ce8d01bd5856770de51fd6fcda94b3a51558ba1f6d5112fa33f4 + languageName: node + linkType: hard + "@sindresorhus/is@npm:^0.14.0": version: 0.14.0 resolution: "@sindresorhus/is@npm:0.14.0" @@ -17388,21 +17509,12 @@ __metadata: languageName: node linkType: hard -"@sinonjs/fake-timers@npm:^10.0.2": - version: 10.3.0 - resolution: "@sinonjs/fake-timers@npm:10.3.0" - dependencies: - "@sinonjs/commons": "npm:^3.0.0" - checksum: 10/78155c7bd866a85df85e22028e046b8d46cf3e840f72260954f5e3ed5bd97d66c595524305a6841ffb3f681a08f6e5cef572a2cce5442a8a232dc29fb409b83e - languageName: node - linkType: hard - -"@sinonjs/fake-timers@npm:^13.0.1": - version: 13.0.2 - resolution: "@sinonjs/fake-timers@npm:13.0.2" +"@sinonjs/fake-timers@npm:^13.0.0, @sinonjs/fake-timers@npm:^13.0.1": + version: 13.0.5 + resolution: "@sinonjs/fake-timers@npm:13.0.5" dependencies: "@sinonjs/commons": "npm:^3.0.1" - checksum: 10/77cca5c548e2529931908c48ac375f162ee901bc52110197b4c470b2535c6c571f9ecd4fa12157f4d2ae174c5391f03940fb563a681a691fb44204a0ef3ded35 + checksum: 10/11ee417968fc4dce1896ab332ac13f353866075a9d2a88ed1f6258f17cc4f7d93e66031b51fcddb8c203aa4d53fd980b0ae18aba06269f4682164878a992ec3f languageName: node linkType: hard @@ -17494,9 +17606,9 @@ __metadata: linkType: hard "@slack/types@npm:^2.11.0, @slack/types@npm:^2.13.0, @slack/types@npm:^2.14.0, @slack/types@npm:^2.18.0": - version: 2.18.0 - resolution: "@slack/types@npm:2.18.0" - checksum: 10/c4a2a2ad0934503a9932779958c2d48af9227d6bb5b1f1568e2efcaac6e612467233962cec86069f13069820a0485d608380bea1dcb23705b1d34a78d108ca0a + version: 2.19.0 + resolution: "@slack/types@npm:2.19.0" + checksum: 10/d5fbc227147b32a964072cb46e2c328b896507a374194532ddf594e18e4ccc2afe9f31f2b92d14ace43ef856b22979523e565616be2c87a2dc6c575d4f919fd0 languageName: node linkType: hard @@ -17520,8 +17632,8 @@ __metadata: linkType: hard "@slack/web-api@npm:^7.5.0": - version: 7.12.0 - resolution: "@slack/web-api@npm:7.12.0" + version: 7.13.0 + resolution: "@slack/web-api@npm:7.13.0" dependencies: "@slack/logger": "npm:^4.0.0" "@slack/types": "npm:^2.18.0" @@ -17535,7 +17647,7 @@ __metadata: p-queue: "npm:^6" p-retry: "npm:^4" retry: "npm:^0.13.1" - checksum: 10/c8936187e1e99a758a13cb33d9aea0b4ecf9d5c78f6d8b7dd1e1cc3e0fb30f50ed718e5270ca6d3c9ccba259f3b1a058c95fc43d35366cdc6ac8cce07742344d + checksum: 10/9e254c372f1b4c3255acd317805f9dfabd38e90d0d8e96f2f79a35957d40d88eed5ba09ec59ae0838dbec32f99bd65ff2773c3528e44cd7aea273dd5e1fd3b82 languageName: node linkType: hard @@ -17549,13 +17661,13 @@ __metadata: languageName: node linkType: hard -"@smithy/abort-controller@npm:^4.2.3": - version: 4.2.3 - resolution: "@smithy/abort-controller@npm:4.2.3" +"@smithy/abort-controller@npm:^4.2.5": + version: 4.2.5 + resolution: "@smithy/abort-controller@npm:4.2.5" dependencies: - "@smithy/types": "npm:^4.8.0" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/0e1b6ea58c876f953cfd953e1dbe8aa58f6fa6530fcec89522922d93a56426943c0b0b488438c12b8994bc50880b7386ad776d308963260f4c2b3edf5d2a7abf + checksum: 10/b0335823347ebbda10a03209ceeef83a711cd0ae8c1fb711e098402f107ea5d056ded24a17cad5a331955110e87377d716b6ed081e86c7b122d6fc6f5b74da67 languageName: node linkType: hard @@ -17591,16 +17703,17 @@ __metadata: languageName: node linkType: hard -"@smithy/config-resolver@npm:^4.3.2, @smithy/config-resolver@npm:^4.3.3": - version: 4.3.3 - resolution: "@smithy/config-resolver@npm:4.3.3" +"@smithy/config-resolver@npm:^4.4.3": + version: 4.4.3 + resolution: "@smithy/config-resolver@npm:4.4.3" dependencies: - "@smithy/node-config-provider": "npm:^4.3.3" - "@smithy/types": "npm:^4.8.0" + "@smithy/node-config-provider": "npm:^4.3.5" + "@smithy/types": "npm:^4.9.0" "@smithy/util-config-provider": "npm:^4.2.0" - "@smithy/util-middleware": "npm:^4.2.3" + "@smithy/util-endpoints": "npm:^3.2.5" + "@smithy/util-middleware": "npm:^4.2.5" tslib: "npm:^2.6.2" - checksum: 10/837e9a5f82aebbc114163e658462dd8a825e107d18687a404ceeb3650b992941f795dcef6b48f43875f6ff6262c7f435247d5c8c5b4c35615a198f3c3b8f7cc2 + checksum: 10/5a00a24d77afed5d820741fbf6f3f523bd4b36c3055cabe79221f0a2bcfa3baebe143997296f0d922707cf29341ec5c8aa4747ad0dba989a59f63f4f79ba5738 languageName: node linkType: hard @@ -17622,21 +17735,21 @@ __metadata: languageName: node linkType: hard -"@smithy/core@npm:^3.16.1, @smithy/core@npm:^3.17.0": - version: 3.17.0 - resolution: "@smithy/core@npm:3.17.0" +"@smithy/core@npm:^3.18.7": + version: 3.18.7 + resolution: "@smithy/core@npm:3.18.7" dependencies: - "@smithy/middleware-serde": "npm:^4.2.3" - "@smithy/protocol-http": "npm:^5.3.3" - "@smithy/types": "npm:^4.8.0" + "@smithy/middleware-serde": "npm:^4.2.6" + "@smithy/protocol-http": "npm:^5.3.5" + "@smithy/types": "npm:^4.9.0" "@smithy/util-base64": "npm:^4.3.0" "@smithy/util-body-length-browser": "npm:^4.2.0" - "@smithy/util-middleware": "npm:^4.2.3" - "@smithy/util-stream": "npm:^4.5.3" + "@smithy/util-middleware": "npm:^4.2.5" + "@smithy/util-stream": "npm:^4.5.6" "@smithy/util-utf8": "npm:^4.2.0" "@smithy/uuid": "npm:^1.1.0" tslib: "npm:^2.6.2" - checksum: 10/9f58db086801f69cba388a6c6e6af172c6f9dc7376497b608ddd25080b14a90eaa8de40cf9e5e3edb68135a3d9b0b35ce1dafb887a51781a59b4e2c2afc0223e + checksum: 10/eb03d40abc3dc8f9e543d3cfbe7066b9126b6b87a1ca7eeb805f1a0ed54dafbdabe4b5ce24eb84ce9f38bebf58ff1070041e7bb6436a1a179558ddebf2851a41 languageName: node linkType: hard @@ -17653,16 +17766,16 @@ __metadata: languageName: node linkType: hard -"@smithy/credential-provider-imds@npm:^4.2.2, @smithy/credential-provider-imds@npm:^4.2.3": - version: 4.2.3 - resolution: "@smithy/credential-provider-imds@npm:4.2.3" +"@smithy/credential-provider-imds@npm:^4.2.5": + version: 4.2.5 + resolution: "@smithy/credential-provider-imds@npm:4.2.5" dependencies: - "@smithy/node-config-provider": "npm:^4.3.3" - "@smithy/property-provider": "npm:^4.2.3" - "@smithy/types": "npm:^4.8.0" - "@smithy/url-parser": "npm:^4.2.3" + "@smithy/node-config-provider": "npm:^4.3.5" + "@smithy/property-provider": "npm:^4.2.5" + "@smithy/types": "npm:^4.9.0" + "@smithy/url-parser": "npm:^4.2.5" tslib: "npm:^2.6.2" - checksum: 10/a654f9c5cd2be7a6faae6e71c0d69d1396037510eb843a3fd6297c81317ab03164a12582d6d24e132bb32f2a0b7c425e92ce963f7169329d8ef8245496a03ef1 + checksum: 10/45ce1b74f3259c073cfbbe8924b91db7e9061c98006476a7a457eb87f84b5687aba34df98513825537d3983e7763a8ce1fa851d24e47ff8731c3100f5defbb52 languageName: node linkType: hard @@ -17734,16 +17847,16 @@ __metadata: languageName: node linkType: hard -"@smithy/fetch-http-handler@npm:^5.3.3, @smithy/fetch-http-handler@npm:^5.3.4": - version: 5.3.4 - resolution: "@smithy/fetch-http-handler@npm:5.3.4" +"@smithy/fetch-http-handler@npm:^5.3.6": + version: 5.3.6 + resolution: "@smithy/fetch-http-handler@npm:5.3.6" dependencies: - "@smithy/protocol-http": "npm:^5.3.3" - "@smithy/querystring-builder": "npm:^4.2.3" - "@smithy/types": "npm:^4.8.0" + "@smithy/protocol-http": "npm:^5.3.5" + "@smithy/querystring-builder": "npm:^4.2.5" + "@smithy/types": "npm:^4.9.0" "@smithy/util-base64": "npm:^4.3.0" tslib: "npm:^2.6.2" - checksum: 10/d72a4981611a5ef7f7f2d9e0ab75c6b2d98dfc09eeab94849f60e61780642221509ff00ca85d73eb2f9d42ea99a14fc9804f25335104d015c57d573fed02262b + checksum: 10/270c8cd7541765ed33f6883d598b3bda1b211c3f8bd4130c5d3db27aa2e5972d6bc46cb9762a673cf62351bb9edf492fc8f863212a6b5abea57f9050738ab3f2 languageName: node linkType: hard @@ -17771,15 +17884,15 @@ __metadata: languageName: node linkType: hard -"@smithy/hash-node@npm:^4.2.2": - version: 4.2.3 - resolution: "@smithy/hash-node@npm:4.2.3" +"@smithy/hash-node@npm:^4.2.5": + version: 4.2.5 + resolution: "@smithy/hash-node@npm:4.2.5" dependencies: - "@smithy/types": "npm:^4.8.0" + "@smithy/types": "npm:^4.9.0" "@smithy/util-buffer-from": "npm:^4.2.0" "@smithy/util-utf8": "npm:^4.2.0" tslib: "npm:^2.6.2" - checksum: 10/7fad11066f36ac4d8c29f3676b26d8ad5aaf82b30ccf417a9eecf2cf8965c2fd0afd1f5f8e28d09112e350c84385fdaa5ed8596a11413a33794923cdc61253de + checksum: 10/cfdcb7459f54c0d7ecad300d24002c6043dfd12b4d3873048b6008a7566a3e8b53bdb141ddb485859ea155e2b4ae648bc21eec5e73ab73cc1aa215c32091c43c languageName: node linkType: hard @@ -17804,13 +17917,13 @@ __metadata: languageName: node linkType: hard -"@smithy/invalid-dependency@npm:^4.2.2": - version: 4.2.3 - resolution: "@smithy/invalid-dependency@npm:4.2.3" +"@smithy/invalid-dependency@npm:^4.2.5": + version: 4.2.5 + resolution: "@smithy/invalid-dependency@npm:4.2.5" dependencies: - "@smithy/types": "npm:^4.8.0" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/2aaac09946401b1665c686e43a08a09cd13fd9d4fac27849db70c685109b5419d06629b248a87921e939549fddc591a35d595855a753df990a0e9dc6ed807d77 + checksum: 10/745b35246c473ec640798a4b3f3fa5fd24618778c4c2244c1917d929ca2e6bf21adc5ef90c8b279b286f917dfa31670a689609546a21a7383c62fec56402b231 languageName: node linkType: hard @@ -17863,14 +17976,14 @@ __metadata: languageName: node linkType: hard -"@smithy/middleware-content-length@npm:^4.2.2": - version: 4.2.3 - resolution: "@smithy/middleware-content-length@npm:4.2.3" +"@smithy/middleware-content-length@npm:^4.2.5": + version: 4.2.5 + resolution: "@smithy/middleware-content-length@npm:4.2.5" dependencies: - "@smithy/protocol-http": "npm:^5.3.3" - "@smithy/types": "npm:^4.8.0" + "@smithy/protocol-http": "npm:^5.3.5" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/dc8dc197435e6a3b95812bfb57431b87c7d41175c8d24372f81e5eeda19448bca2fd2cb8829788e33abe3833e32da66e3fc44482ede3c929e04eec31bb74244d + checksum: 10/2c622812c4379f1957669ddd97a3daaf99302534e6f7a3cc2f220d65f56b84451a6d2e1d78dd79712b074ecdcf4bcbd9796b73af0c15df9245b192a747d410a7 languageName: node linkType: hard @@ -17889,19 +18002,19 @@ __metadata: languageName: node linkType: hard -"@smithy/middleware-endpoint@npm:^4.3.3, @smithy/middleware-endpoint@npm:^4.3.4": - version: 4.3.4 - resolution: "@smithy/middleware-endpoint@npm:4.3.4" +"@smithy/middleware-endpoint@npm:^4.3.14": + version: 4.3.14 + resolution: "@smithy/middleware-endpoint@npm:4.3.14" dependencies: - "@smithy/core": "npm:^3.17.0" - "@smithy/middleware-serde": "npm:^4.2.3" - "@smithy/node-config-provider": "npm:^4.3.3" - "@smithy/shared-ini-file-loader": "npm:^4.3.3" - "@smithy/types": "npm:^4.8.0" - "@smithy/url-parser": "npm:^4.2.3" - "@smithy/util-middleware": "npm:^4.2.3" + "@smithy/core": "npm:^3.18.7" + "@smithy/middleware-serde": "npm:^4.2.6" + "@smithy/node-config-provider": "npm:^4.3.5" + "@smithy/shared-ini-file-loader": "npm:^4.4.0" + "@smithy/types": "npm:^4.9.0" + "@smithy/url-parser": "npm:^4.2.5" + "@smithy/util-middleware": "npm:^4.2.5" tslib: "npm:^2.6.2" - checksum: 10/018dae712791d553bf9484b4183b6b6162da0d9a9baf6d8c39fae5669a6adf764b294edcf63827c119fed1da0bbfa3f751f4796d35379d13b8ac36504c449685 + checksum: 10/2622afb558bdc89a459e6220a16406d614da9c89d002e4b9eff6255739a887fc7eabbd2af755446730e8eb17b1268421842de4abc053c7a0c8693fb423c1108e languageName: node linkType: hard @@ -17922,20 +18035,20 @@ __metadata: languageName: node linkType: hard -"@smithy/middleware-retry@npm:^4.4.3": - version: 4.4.4 - resolution: "@smithy/middleware-retry@npm:4.4.4" +"@smithy/middleware-retry@npm:^4.4.14": + version: 4.4.14 + resolution: "@smithy/middleware-retry@npm:4.4.14" dependencies: - "@smithy/node-config-provider": "npm:^4.3.3" - "@smithy/protocol-http": "npm:^5.3.3" - "@smithy/service-error-classification": "npm:^4.2.3" - "@smithy/smithy-client": "npm:^4.9.0" - "@smithy/types": "npm:^4.8.0" - "@smithy/util-middleware": "npm:^4.2.3" - "@smithy/util-retry": "npm:^4.2.3" + "@smithy/node-config-provider": "npm:^4.3.5" + "@smithy/protocol-http": "npm:^5.3.5" + "@smithy/service-error-classification": "npm:^4.2.5" + "@smithy/smithy-client": "npm:^4.9.10" + "@smithy/types": "npm:^4.9.0" + "@smithy/util-middleware": "npm:^4.2.5" + "@smithy/util-retry": "npm:^4.2.5" "@smithy/uuid": "npm:^1.1.0" tslib: "npm:^2.6.2" - checksum: 10/850477842b63bf6b7d362a777eee68db0d11578b3437a3ced55b3c9531622bb126531e7b9b3b4ab3627e59780f86bb3be690eb2ba0aa34fc1bf13cb29dad54c1 + checksum: 10/4e5eee34c97ca971914b684cd0d5eeeca5213893f6c7b29b029d4b9a516458eda52bff7d6e2470de8d7e89c47cabb7bbaa0101e5d1f58740f1078b26b9abbd4d languageName: node linkType: hard @@ -17949,14 +18062,14 @@ __metadata: languageName: node linkType: hard -"@smithy/middleware-serde@npm:^4.2.2, @smithy/middleware-serde@npm:^4.2.3": - version: 4.2.3 - resolution: "@smithy/middleware-serde@npm:4.2.3" +"@smithy/middleware-serde@npm:^4.2.6": + version: 4.2.6 + resolution: "@smithy/middleware-serde@npm:4.2.6" dependencies: - "@smithy/protocol-http": "npm:^5.3.3" - "@smithy/types": "npm:^4.8.0" + "@smithy/protocol-http": "npm:^5.3.5" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/725583757ed653628887c72a3924e11eda9de88a72ca337538fb3365f1cb732c0652129d15033560548178bcccf7964211926340f56fdd972a28dab549b32dc1 + checksum: 10/87aa0c6bf9743d003fed4f1d544a69610dae0005a58241b4fa8aab1c48746f104bbbb549df7f89b7040134a2af645ebecc375b3abde26c910d5e4e4c26c28b88 languageName: node linkType: hard @@ -17970,13 +18083,13 @@ __metadata: languageName: node linkType: hard -"@smithy/middleware-stack@npm:^4.2.2, @smithy/middleware-stack@npm:^4.2.3": - version: 4.2.3 - resolution: "@smithy/middleware-stack@npm:4.2.3" +"@smithy/middleware-stack@npm:^4.2.5": + version: 4.2.5 + resolution: "@smithy/middleware-stack@npm:4.2.5" dependencies: - "@smithy/types": "npm:^4.8.0" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/f6054dbb2b3f141390940caa93319c750f447da483b36d506208200023100eeda5a550b9d3415812ad194604c54cae48aa2e185f6c3ab625e87718089d75b3ab + checksum: 10/146647dabe0bc20415b82a0862b1d65ceb4f98167fff19f7002d6a8ea933527c19b5081cd4fdc4532f803e8085356e987d72c659501dccbdd8c8056a7d394ce7 languageName: node linkType: hard @@ -17992,15 +18105,15 @@ __metadata: languageName: node linkType: hard -"@smithy/node-config-provider@npm:^4.3.2, @smithy/node-config-provider@npm:^4.3.3": - version: 4.3.3 - resolution: "@smithy/node-config-provider@npm:4.3.3" +"@smithy/node-config-provider@npm:^4.3.5": + version: 4.3.5 + resolution: "@smithy/node-config-provider@npm:4.3.5" dependencies: - "@smithy/property-provider": "npm:^4.2.3" - "@smithy/shared-ini-file-loader": "npm:^4.3.3" - "@smithy/types": "npm:^4.8.0" + "@smithy/property-provider": "npm:^4.2.5" + "@smithy/shared-ini-file-loader": "npm:^4.4.0" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/0c2191861f5bcb4a0e1d5b15968c4edeb33a910948ee8378a3504311ee4da8f53a29d223ee86433b693cbd6aac9977560e62a5ef2fae108904408f4bf8b89e55 + checksum: 10/d0044a205b765be9648c44654e189804bf1cf83aed002fd5ff1cb7abbc9ed4b29886bb9b9c87fe440b8c319b3e1e2aecafec5aa147569d5921e35b1e2692f010 languageName: node linkType: hard @@ -18017,16 +18130,16 @@ __metadata: languageName: node linkType: hard -"@smithy/node-http-handler@npm:^4.4.1, @smithy/node-http-handler@npm:^4.4.2": - version: 4.4.2 - resolution: "@smithy/node-http-handler@npm:4.4.2" +"@smithy/node-http-handler@npm:^4.4.5": + version: 4.4.5 + resolution: "@smithy/node-http-handler@npm:4.4.5" dependencies: - "@smithy/abort-controller": "npm:^4.2.3" - "@smithy/protocol-http": "npm:^5.3.3" - "@smithy/querystring-builder": "npm:^4.2.3" - "@smithy/types": "npm:^4.8.0" + "@smithy/abort-controller": "npm:^4.2.5" + "@smithy/protocol-http": "npm:^5.3.5" + "@smithy/querystring-builder": "npm:^4.2.5" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/82d08f563dad787a2685c3dbc2ae8d15d11f02bce2a5ba3e078b8093e2324f6a3e15ff5730cacc0a5bbd1c096134497da15c03bd0e03c73041205f8d84621910 + checksum: 10/0748c69d581c01144360c81aa3f0eeb619c3e473540846a59949031b25bbd4a83e82beabd801c4bc9b4774f53a2e1ab00a5d3afb847f7a245788630b7c3b307a languageName: node linkType: hard @@ -18040,13 +18153,13 @@ __metadata: languageName: node linkType: hard -"@smithy/property-provider@npm:^4.2.2, @smithy/property-provider@npm:^4.2.3": - version: 4.2.3 - resolution: "@smithy/property-provider@npm:4.2.3" +"@smithy/property-provider@npm:^4.2.5": + version: 4.2.5 + resolution: "@smithy/property-provider@npm:4.2.5" dependencies: - "@smithy/types": "npm:^4.8.0" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/01368d05e878235fbda10f2d2b6c1d004d918ed84fcf01d26ad4765f4a6c7c6d889a0f3e08c786cf977f02c2eb53ae2fb01ac511c5f538ac6daa0170d41dbd7d + checksum: 10/fc7b1b26f4a0ae3bdf3e742607cc1fa4c81dbf166bdd908a528e2613dd76923722e927be66edd4ddcc203ca2c33a32c0d0088dd89132ef4feb265d7fa43b44da languageName: node linkType: hard @@ -18060,13 +18173,13 @@ __metadata: languageName: node linkType: hard -"@smithy/protocol-http@npm:^5.3.2, @smithy/protocol-http@npm:^5.3.3": - version: 5.3.3 - resolution: "@smithy/protocol-http@npm:5.3.3" +"@smithy/protocol-http@npm:^5.3.5": + version: 5.3.5 + resolution: "@smithy/protocol-http@npm:5.3.5" dependencies: - "@smithy/types": "npm:^4.8.0" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/224043361976827a5498809e0d9fe7c5fd8a8233da40f05b8113cf5358e6bc0d4a4e6160d74ffdadb0c9733fd88cab32625d324cfd45748beb19fa69201410fb + checksum: 10/444c40f8a0cdd2b7d73a0d48527903f5a76346e93b9c1a2d9fd2b45a925259eb2afce2c3ce384a0f52eda8c82ffcd5ef137ac52580fd99f7b20c93d54191baf2 languageName: node linkType: hard @@ -18081,14 +18194,14 @@ __metadata: languageName: node linkType: hard -"@smithy/querystring-builder@npm:^4.2.3": - version: 4.2.3 - resolution: "@smithy/querystring-builder@npm:4.2.3" +"@smithy/querystring-builder@npm:^4.2.5": + version: 4.2.5 + resolution: "@smithy/querystring-builder@npm:4.2.5" dependencies: - "@smithy/types": "npm:^4.8.0" + "@smithy/types": "npm:^4.9.0" "@smithy/util-uri-escape": "npm:^4.2.0" tslib: "npm:^2.6.2" - checksum: 10/b0f7733d66b9e821d1efe260dc44db5586c3f8b1ebe59d30ff1fcf5d3dab87a1fa133f005e9947e7127dbfa6e504581bd2507b37c6568889ab2a99dceacb4064 + checksum: 10/34385aa700bd4476ac40901b3be11c389c898a54a84c6a109794493d9bd285d0e6fe24c8d4cc696be04e0cd0b105985cc244fe36a00a99db04ec6f81e11cae69 languageName: node linkType: hard @@ -18102,13 +18215,13 @@ __metadata: languageName: node linkType: hard -"@smithy/querystring-parser@npm:^4.2.3": - version: 4.2.3 - resolution: "@smithy/querystring-parser@npm:4.2.3" +"@smithy/querystring-parser@npm:^4.2.5": + version: 4.2.5 + resolution: "@smithy/querystring-parser@npm:4.2.5" dependencies: - "@smithy/types": "npm:^4.8.0" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/39bd2f57cf1c695f2942b4dd5362dd80ad2d2fd40d03e90396721735637190bd05ea9e942639b67ae8817a9e99fff9b510f6ba04568f14ad62228e676994107a + checksum: 10/4d3c12436409be000c865fe46fe792454d518b9939971d5f8f19d73b7e04ce26654c13555a81eaf72092bc92cdb0dddc762891f1e73785c0827933b95b81b42a languageName: node linkType: hard @@ -18121,12 +18234,12 @@ __metadata: languageName: node linkType: hard -"@smithy/service-error-classification@npm:^4.2.3": - version: 4.2.3 - resolution: "@smithy/service-error-classification@npm:4.2.3" +"@smithy/service-error-classification@npm:^4.2.5": + version: 4.2.5 + resolution: "@smithy/service-error-classification@npm:4.2.5" dependencies: - "@smithy/types": "npm:^4.8.0" - checksum: 10/fcb61c68fb820c379ef3e86d6b316c10b706ef76f23f19c66323555b9973cec34a237caabcb67fb5b5553f93ddb52acd1d2dbbe6a9f64806584089588c203c38 + "@smithy/types": "npm:^4.9.0" + checksum: 10/4466f742bbc960411deee6424d15525ed4cc8cc024c82c8023f380213c3e17d3eee57db91428df3b1b55281a463b8178d5f69100de48aaf0fd0e39ff81017bff languageName: node linkType: hard @@ -18140,13 +18253,13 @@ __metadata: languageName: node linkType: hard -"@smithy/shared-ini-file-loader@npm:^4.3.2, @smithy/shared-ini-file-loader@npm:^4.3.3": - version: 4.3.3 - resolution: "@smithy/shared-ini-file-loader@npm:4.3.3" +"@smithy/shared-ini-file-loader@npm:^4.4.0": + version: 4.4.0 + resolution: "@smithy/shared-ini-file-loader@npm:4.4.0" dependencies: - "@smithy/types": "npm:^4.8.0" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/2b724e27e7e7ff58be6a75985856f3cb11b3830cd04eab3375743ab961fa9a8a5bb5efa6cb40f89afc73702c6daa36c415153f460e4ec6b08394aa4101a1ec41 + checksum: 10/09dd2fc84ce8c356995802388a3fb2d954c7a356e7d62e059ed969e28e79d8b07d84109bca81e2b2447d7f1a57f7e800bd14138552cba163253f30f5345ff524 languageName: node linkType: hard @@ -18166,19 +18279,19 @@ __metadata: languageName: node linkType: hard -"@smithy/signature-v4@npm:^5.3.2": - version: 5.3.3 - resolution: "@smithy/signature-v4@npm:5.3.3" +"@smithy/signature-v4@npm:^5.3.5": + version: 5.3.5 + resolution: "@smithy/signature-v4@npm:5.3.5" dependencies: "@smithy/is-array-buffer": "npm:^4.2.0" - "@smithy/protocol-http": "npm:^5.3.3" - "@smithy/types": "npm:^4.8.0" + "@smithy/protocol-http": "npm:^5.3.5" + "@smithy/types": "npm:^4.9.0" "@smithy/util-hex-encoding": "npm:^4.2.0" - "@smithy/util-middleware": "npm:^4.2.3" + "@smithy/util-middleware": "npm:^4.2.5" "@smithy/util-uri-escape": "npm:^4.2.0" "@smithy/util-utf8": "npm:^4.2.0" tslib: "npm:^2.6.2" - checksum: 10/75c22765749fbcfe34110d745afc346aa6b6d62d2ede74e062be48e65704ff92abf1695c1e1323b211a813f355849aaeb2caa0cab3c92a34112512c51728e9ab + checksum: 10/aa615f431d436c6ccdee159e9fea15b56e7754bf713f9c0d6e0d75a915abe8a938a0b3eb36306c2ae9cd0713198ea38410f05f60bcf58ee68bbf22ae9639ce39 languageName: node linkType: hard @@ -18196,18 +18309,18 @@ __metadata: languageName: node linkType: hard -"@smithy/smithy-client@npm:^4.8.1, @smithy/smithy-client@npm:^4.9.0": - version: 4.9.0 - resolution: "@smithy/smithy-client@npm:4.9.0" +"@smithy/smithy-client@npm:^4.9.10": + version: 4.9.10 + resolution: "@smithy/smithy-client@npm:4.9.10" dependencies: - "@smithy/core": "npm:^3.17.0" - "@smithy/middleware-endpoint": "npm:^4.3.4" - "@smithy/middleware-stack": "npm:^4.2.3" - "@smithy/protocol-http": "npm:^5.3.3" - "@smithy/types": "npm:^4.8.0" - "@smithy/util-stream": "npm:^4.5.3" + "@smithy/core": "npm:^3.18.7" + "@smithy/middleware-endpoint": "npm:^4.3.14" + "@smithy/middleware-stack": "npm:^4.2.5" + "@smithy/protocol-http": "npm:^5.3.5" + "@smithy/types": "npm:^4.9.0" + "@smithy/util-stream": "npm:^4.5.6" tslib: "npm:^2.6.2" - checksum: 10/b1533d0faeaa810b87997ddc40bdda64a49af24b10fdb88ef37d1a45743b1d8eb292b7835e1da4c3f5facf47e4cc7caec7e2ccceae46ddb529257419b207086c + checksum: 10/c4e593f1e91da83c1e144d1c2e85ae88327ac99d1b11b7f1dfb6b76a63029ae3f73396893834c6beae017be0308ecab5c4c1f5449ab6aff11f55cebaa911d256 languageName: node linkType: hard @@ -18229,12 +18342,12 @@ __metadata: languageName: node linkType: hard -"@smithy/types@npm:^4.7.1, @smithy/types@npm:^4.8.0": - version: 4.8.0 - resolution: "@smithy/types@npm:4.8.0" +"@smithy/types@npm:^4.9.0": + version: 4.9.0 + resolution: "@smithy/types@npm:4.9.0" dependencies: tslib: "npm:^2.6.2" - checksum: 10/12ee5d629cae55b696e870caefbdbebc774deffee4171d0543552b6f8ce06ac1259fa3cf93e9be767f5e1f3c59c7ffa28a6d3dbe0a8f0c8e7811d3acbf3ab87c + checksum: 10/b966ddb05487ee634555d248c83838012c4d1cbbd17c28799743dea65ef3c597695a1a47c9cb466e1ce5dbdd1e4bb23f0b4cdfb3595e12fd892a31c275a04ea5 languageName: node linkType: hard @@ -18249,14 +18362,14 @@ __metadata: languageName: node linkType: hard -"@smithy/url-parser@npm:^4.2.2, @smithy/url-parser@npm:^4.2.3": - version: 4.2.3 - resolution: "@smithy/url-parser@npm:4.2.3" +"@smithy/url-parser@npm:^4.2.5": + version: 4.2.5 + resolution: "@smithy/url-parser@npm:4.2.5" dependencies: - "@smithy/querystring-parser": "npm:^4.2.3" - "@smithy/types": "npm:^4.8.0" + "@smithy/querystring-parser": "npm:^4.2.5" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/e8af96bb56b5cc18841a731ec01c16ea420628099c4c090c65f0a9867e693a53fa95a7b05fac60b9ac0ef1bfad9308bf13e68ac854d8fb62f599a3dcd164203d + checksum: 10/f2649f43f9f569f90c3e68023e938f80fd43fbfce2c454162507ba7d39497f2f89e9ade2369ddc07d9aa4b7ebd7dc80b22922de8488bef04f447467949f62fc3 languageName: node linkType: hard @@ -18379,15 +18492,15 @@ __metadata: languageName: node linkType: hard -"@smithy/util-defaults-mode-browser@npm:^4.3.2": - version: 4.3.3 - resolution: "@smithy/util-defaults-mode-browser@npm:4.3.3" +"@smithy/util-defaults-mode-browser@npm:^4.3.13": + version: 4.3.13 + resolution: "@smithy/util-defaults-mode-browser@npm:4.3.13" dependencies: - "@smithy/property-provider": "npm:^4.2.3" - "@smithy/smithy-client": "npm:^4.9.0" - "@smithy/types": "npm:^4.8.0" + "@smithy/property-provider": "npm:^4.2.5" + "@smithy/smithy-client": "npm:^4.9.10" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/74efd0f09dc5638c1f183eb976f7293706dc37d506e23f534a0042324515e57d0e34ac6b65b75af2d1cc2f81bcec6626167d1bd231b278bb2505ae7c29eb0578 + checksum: 10/a42116ddd4638e12137d520ace2d2acc32708a98d79de06a91ef949d2163f7246bfd400faad8a1ab5f0f5a5a55d4d73b9b7d70edd2b36d83fdb9e71d1b06e56e languageName: node linkType: hard @@ -18406,18 +18519,18 @@ __metadata: languageName: node linkType: hard -"@smithy/util-defaults-mode-node@npm:^4.2.3": - version: 4.2.4 - resolution: "@smithy/util-defaults-mode-node@npm:4.2.4" +"@smithy/util-defaults-mode-node@npm:^4.2.16": + version: 4.2.16 + resolution: "@smithy/util-defaults-mode-node@npm:4.2.16" dependencies: - "@smithy/config-resolver": "npm:^4.3.3" - "@smithy/credential-provider-imds": "npm:^4.2.3" - "@smithy/node-config-provider": "npm:^4.3.3" - "@smithy/property-provider": "npm:^4.2.3" - "@smithy/smithy-client": "npm:^4.9.0" - "@smithy/types": "npm:^4.8.0" + "@smithy/config-resolver": "npm:^4.4.3" + "@smithy/credential-provider-imds": "npm:^4.2.5" + "@smithy/node-config-provider": "npm:^4.3.5" + "@smithy/property-provider": "npm:^4.2.5" + "@smithy/smithy-client": "npm:^4.9.10" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/2c89802f37ee74803d3aa151c628b6d6479c42546582e50924a93e232b3b3ea289a8373833479f92f940cfe57c48714a4be03e78cea6cdcaa001ef482126b785 + checksum: 10/9c466504abf0b8ef37bf4a72e85b1d164dcd9e8426cb30e9bbff46b0fdb536cfab21c8f5650e4500bd9a120749b462c345b681fef72d61ef7331a835c4b007ff languageName: node linkType: hard @@ -18432,14 +18545,14 @@ __metadata: languageName: node linkType: hard -"@smithy/util-endpoints@npm:^3.2.2": - version: 3.2.3 - resolution: "@smithy/util-endpoints@npm:3.2.3" +"@smithy/util-endpoints@npm:^3.2.5": + version: 3.2.5 + resolution: "@smithy/util-endpoints@npm:3.2.5" dependencies: - "@smithy/node-config-provider": "npm:^4.3.3" - "@smithy/types": "npm:^4.8.0" + "@smithy/node-config-provider": "npm:^4.3.5" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/55b94c1ad0b59ca92ba0fb8510bd981d9e82cc178425ed681172418993be4744371d8408fa5ac70a88019219534bed8cba4d42177bfadd0bb6d474e701161452 + checksum: 10/3a91786650d007d6dd529107960771bb81d5fb5aef9a2189edfab6ecbfdbd673d72c8d6c1fe65c7814d34244bf448bccd7f64796646d41281b7ba18d37bdd967 languageName: node linkType: hard @@ -18471,13 +18584,13 @@ __metadata: languageName: node linkType: hard -"@smithy/util-middleware@npm:^4.2.2, @smithy/util-middleware@npm:^4.2.3": - version: 4.2.3 - resolution: "@smithy/util-middleware@npm:4.2.3" +"@smithy/util-middleware@npm:^4.2.5": + version: 4.2.5 + resolution: "@smithy/util-middleware@npm:4.2.5" dependencies: - "@smithy/types": "npm:^4.8.0" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/3f029d65bb40c7b4555590e502d51942bde0bd413ac691417f09b7cbe384ddb22cb12976ee6c652beac76a8652927caa64c00a743a441639df0d4eb1fecc9c79 + checksum: 10/845feb06378be04902ae371daf94bd90a739dde0cb3780f3b438c70d8af7d69a65cb231946e40e65a8ead1488b716509a197a9716d4faa200dd32a385ea8387c languageName: node linkType: hard @@ -18492,14 +18605,14 @@ __metadata: languageName: node linkType: hard -"@smithy/util-retry@npm:^4.2.2, @smithy/util-retry@npm:^4.2.3": - version: 4.2.3 - resolution: "@smithy/util-retry@npm:4.2.3" +"@smithy/util-retry@npm:^4.2.5": + version: 4.2.5 + resolution: "@smithy/util-retry@npm:4.2.5" dependencies: - "@smithy/service-error-classification": "npm:^4.2.3" - "@smithy/types": "npm:^4.8.0" + "@smithy/service-error-classification": "npm:^4.2.5" + "@smithy/types": "npm:^4.9.0" tslib: "npm:^2.6.2" - checksum: 10/7907a7ef3f766984c85438240b8a46776f5f0b385d1660b7c261e2ba50947dfbe70ba4924c47e11c2937add998c99cf97639c899b0bae476dfd18fe32125afe8 + checksum: 10/e2715477f2021327e6cc3a796955d74659cbe18e264969cb1188964b82b19fa03eca82d9fe51dc3ca5839d9c654c0357dc670c85543402150536281ef3c14402 languageName: node linkType: hard @@ -18519,19 +18632,19 @@ __metadata: languageName: node linkType: hard -"@smithy/util-stream@npm:^4.5.2, @smithy/util-stream@npm:^4.5.3": - version: 4.5.3 - resolution: "@smithy/util-stream@npm:4.5.3" +"@smithy/util-stream@npm:^4.5.6": + version: 4.5.6 + resolution: "@smithy/util-stream@npm:4.5.6" dependencies: - "@smithy/fetch-http-handler": "npm:^5.3.4" - "@smithy/node-http-handler": "npm:^4.4.2" - "@smithy/types": "npm:^4.8.0" + "@smithy/fetch-http-handler": "npm:^5.3.6" + "@smithy/node-http-handler": "npm:^4.4.5" + "@smithy/types": "npm:^4.9.0" "@smithy/util-base64": "npm:^4.3.0" "@smithy/util-buffer-from": "npm:^4.2.0" "@smithy/util-hex-encoding": "npm:^4.2.0" "@smithy/util-utf8": "npm:^4.2.0" tslib: "npm:^2.6.2" - checksum: 10/d898b1c45f635fe606f53718f2e1c812a6c74f5e0e2524ec494816d4dee7635d1c59436a90b679f46419079e5053d311a84f1d707354a08d11c4708ba502c806 + checksum: 10/6406467f64a39eae77df30ea6e45c13988aff86b5de5b3f4f91929fa8eb6cd786cbda24832e489ecaa559fb9d1514d6def86765c0f548e966cbdaa98cb3a8d77 languageName: node linkType: hard @@ -18640,19 +18753,6 @@ __metadata: languageName: node linkType: hard -"@snyk/github-codeowners@npm:1.1.0": - version: 1.1.0 - resolution: "@snyk/github-codeowners@npm:1.1.0" - dependencies: - commander: "npm:^4.1.1" - ignore: "npm:^5.1.8" - p-map: "npm:^4.0.0" - bin: - github-codeowners: dist/cli.js - checksum: 10/34120ef622616fef1ed8af12869d8c1803842aafa3fbacca263805ee7c85f58d11bdc301ef698c9b41268b275b9fd090f5d9f6d89c556abe9d52196e72d1c510 - languageName: node - linkType: hard - "@snyk/graphlib@npm:2.1.9-patch.3": version: 2.1.9-patch.3 resolution: "@snyk/graphlib@npm:2.1.9-patch.3" @@ -18676,6 +18776,16 @@ __metadata: languageName: node linkType: hard +"@so-ric/colorspace@npm:^1.1.6": + version: 1.1.6 + resolution: "@so-ric/colorspace@npm:1.1.6" + dependencies: + color: "npm:^5.0.2" + text-hex: "npm:1.0.x" + checksum: 10/fc3285e5cb9a458d255aa678d9453174ca40689a4c692f1617907996ab8eb78839542439604ced484c4f674a5297f7ba8b0e63fcfe901174f43c3d9c3c881b52 + languageName: node + linkType: hard + "@spotify/eslint-config-base@npm:^15.0.0": version: 15.0.0 resolution: "@spotify/eslint-config-base@npm:15.0.0" @@ -18993,82 +19103,121 @@ __metadata: languageName: node linkType: hard -"@storybook/addon-a11y@npm:^9.1.7": - version: 9.1.16 - resolution: "@storybook/addon-a11y@npm:9.1.16" +"@storybook/addon-a11y@npm:^10.1.9": + version: 10.1.9 + resolution: "@storybook/addon-a11y@npm:10.1.9" dependencies: "@storybook/global": "npm:^5.0.0" axe-core: "npm:^4.2.0" peerDependencies: - storybook: ^9.1.16 - checksum: 10/24645a9df98aa52f7e52ac0ba5a87eb5b3ac533130b490628925a657ddbaab9ac370930594f5a879b9c30a36f0d2f9b752e9643a57ac8f2f835379143afb2f06 + storybook: ^10.1.9 + checksum: 10/d004f9975d20368ce58bb6c25bc421920ed6898ae2342a821776c41b6ed7597e41f7bfe932fc6be73f7611ba053cdcd63b18cf7c9ec0e5375a0c01fe45635b0f languageName: node linkType: hard -"@storybook/addon-docs@npm:^9.1.7": - version: 9.1.16 - resolution: "@storybook/addon-docs@npm:9.1.16" +"@storybook/addon-docs@npm:^10.1.9": + version: 10.1.9 + resolution: "@storybook/addon-docs@npm:10.1.9" dependencies: "@mdx-js/react": "npm:^3.0.0" - "@storybook/csf-plugin": "npm:9.1.16" - "@storybook/icons": "npm:^1.4.0" - "@storybook/react-dom-shim": "npm:9.1.16" + "@storybook/csf-plugin": "npm:10.1.9" + "@storybook/icons": "npm:^2.0.0" + "@storybook/react-dom-shim": "npm:10.1.9" react: "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" react-dom: "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^9.1.16 - checksum: 10/02081dd9c9e6273e8030b7ab24470d1d29513870245aaf2741cd97eea1350918db354fb78484f192c191942d75f834e81176717e59aefc28572c88909979e8a9 + storybook: ^10.1.9 + checksum: 10/4f24aa255b2cc3c5ad1560730e40aba5f2019a8069456e1f03043cf67de14cbb9ac26650eb805fddb6395182db62d850df127fc310a1fff49c39cf166a63aa02 languageName: node linkType: hard -"@storybook/addon-links@npm:^9.1.7": - version: 9.1.16 - resolution: "@storybook/addon-links@npm:9.1.16" +"@storybook/addon-links@npm:^10.1.9": + version: 10.1.9 + resolution: "@storybook/addon-links@npm:10.1.9" dependencies: "@storybook/global": "npm:^5.0.0" peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^9.1.16 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.1.9 peerDependenciesMeta: react: optional: true - checksum: 10/04eca604043d55c391512bf5e63cb742c84759eedb535f3deb8b1e9fcc68fb0cf8222df4990cefba34cae8321a6ecd380be34111ebc6b6a109dd5e910fe0a5a7 + checksum: 10/c0f9c97b64dd50e0ea0642eb75e2f7af81548958aeef35e1d44992c7d85ee8796cc034088aebba5e81b760932e064e39b7bdc64f572bbd68d237f6870c95f52e languageName: node linkType: hard -"@storybook/addon-themes@npm:^9.1.7": - version: 9.1.16 - resolution: "@storybook/addon-themes@npm:9.1.16" +"@storybook/addon-themes@npm:^10.1.9": + version: 10.1.9 + resolution: "@storybook/addon-themes@npm:10.1.9" dependencies: ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^9.1.16 - checksum: 10/c596f386a6aad3638febcc2df4d53e5e185c8c7b7e1bef17dd12b83a9ad6102769b2244869065912fd5c563915baf52b631a872fff239c80539ac49e5daad85e + storybook: ^10.1.9 + checksum: 10/66d231efb94919aa89bbb92291925d96519c993d6d75326850caad432a1425c4aa93b4249bf46fffc5b41724ef007d01477045a74e55e937410d8fa7be7037f6 languageName: node linkType: hard -"@storybook/builder-vite@npm:9.1.16": - version: 9.1.16 - resolution: "@storybook/builder-vite@npm:9.1.16" +"@storybook/addon-vitest@npm:^10.1.9": + version: 10.1.9 + resolution: "@storybook/addon-vitest@npm:10.1.9" dependencies: - "@storybook/csf-plugin": "npm:9.1.16" + "@storybook/global": "npm:^5.0.0" + "@storybook/icons": "npm:^2.0.0" + peerDependencies: + "@vitest/browser": ^3.0.0 || ^4.0.0 + "@vitest/browser-playwright": ^4.0.0 + "@vitest/runner": ^3.0.0 || ^4.0.0 + storybook: ^10.1.9 + vitest: ^3.0.0 || ^4.0.0 + peerDependenciesMeta: + "@vitest/browser": + optional: true + "@vitest/browser-playwright": + optional: true + "@vitest/runner": + optional: true + vitest: + optional: true + checksum: 10/8d1be8902a243d6c42a84d0acd1550014e5fa6cb509cdfbec6603e6b3d34def5f28d483f8d4289ef7be87d48f41a47f34f4f7028eef7add49cec5d98cf477a0d + languageName: node + linkType: hard + +"@storybook/builder-vite@npm:10.1.9": + version: 10.1.9 + resolution: "@storybook/builder-vite@npm:10.1.9" + dependencies: + "@storybook/csf-plugin": "npm:10.1.9" + "@vitest/mocker": "npm:3.2.4" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^9.1.16 + storybook: ^10.1.9 vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10/bd30ad08a0222aa0d44a4127e959402058c7aedf7dddf3114a4ad54cd49384c24f738fe61e3e9be7be17943557e9ccbdd7311101f69af98d83c6eca5e433c855 + checksum: 10/4dc086193b45219b24e528404ed51c4dbc240a9ac20feeef88a1b051ae286264006741b402f4936745f5733c8b568e090b029aaea73bf5e3a078047aa957f8ba languageName: node linkType: hard -"@storybook/csf-plugin@npm:9.1.16": - version: 9.1.16 - resolution: "@storybook/csf-plugin@npm:9.1.16" +"@storybook/csf-plugin@npm:10.1.9": + version: 10.1.9 + resolution: "@storybook/csf-plugin@npm:10.1.9" dependencies: - unplugin: "npm:^1.3.1" + unplugin: "npm:^2.3.5" peerDependencies: - storybook: ^9.1.16 - checksum: 10/81612bfa904673d5a28094dce40cc0890091f6e3a82bdcedc34dd43c103d7f20d838c81744f87e65a9efb7b98779b4d89ecc95a14a5b550efe2aeff53de0c6ea + esbuild: "*" + rollup: "*" + storybook: ^10.1.9 + vite: "*" + webpack: "*" + peerDependenciesMeta: + esbuild: + optional: true + rollup: + optional: true + vite: + optional: true + webpack: + optional: true + checksum: 10/604339107f7bd57f64b5b0d9ddc16a447fc0987d348cb9add5bb2fc75b568760afb3689cd51c73df48cc13bf2d82386f89da57e53f500687434d403cd7395e49 languageName: node linkType: hard @@ -19079,392 +19228,393 @@ __metadata: languageName: node linkType: hard -"@storybook/icons@npm:^1.4.0": - version: 1.4.0 - resolution: "@storybook/icons@npm:1.4.0" +"@storybook/icons@npm:^2.0.0": + version: 2.0.1 + resolution: "@storybook/icons@npm:2.0.1" peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - checksum: 10/513d36188bb84f1f64dd982b6bd577de4c3d7bc2aa9d318a2acf8c654d16994bac0b669787a4ced6720e471251bcb0ec68b8d8dc2cc7245d11102bd617243700 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10/04ffa285f4defc611def51f82492688bc49f6f4e8ce4e7ba5c99a1c1410b7e8820b5da65c33610a497df2409de7b48fae399052c5cacab6a4a4a9b48a36ebfd5 languageName: node linkType: hard -"@storybook/react-dom-shim@npm:9.1.16": - version: 9.1.16 - resolution: "@storybook/react-dom-shim@npm:9.1.16" +"@storybook/react-dom-shim@npm:10.1.9": + version: 10.1.9 + resolution: "@storybook/react-dom-shim@npm:10.1.9" peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^9.1.16 - checksum: 10/05c1426a02416b353e61f267d901ce938919a2cc1e94eebe11938c15466e1d584034421798cc228a00cfafeb3aa2bf28cf2814bdf328896a117734c4c8a6890e + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.1.9 + checksum: 10/2b6e598eb8ddac80fd6cd4d888b3a8da1c352e9ffc666a1bcce6f4ac404c77cc75571a57b433e1a12f34cfd255db31fb399a7f38e70bde5d197de2d072eeac85 languageName: node linkType: hard -"@storybook/react-vite@npm:^9.1.7": - version: 9.1.16 - resolution: "@storybook/react-vite@npm:9.1.16" +"@storybook/react-vite@npm:^10.1.9": + version: 10.1.9 + resolution: "@storybook/react-vite@npm:10.1.9" dependencies: - "@joshwooding/vite-plugin-react-docgen-typescript": "npm:0.6.1" + "@joshwooding/vite-plugin-react-docgen-typescript": "npm:^0.6.3" "@rollup/pluginutils": "npm:^5.0.2" - "@storybook/builder-vite": "npm:9.1.16" - "@storybook/react": "npm:9.1.16" - find-up: "npm:^7.0.0" + "@storybook/builder-vite": "npm:10.1.9" + "@storybook/react": "npm:10.1.9" + empathic: "npm:^2.0.0" magic-string: "npm:^0.30.0" react-docgen: "npm:^8.0.0" resolve: "npm:^1.22.8" tsconfig-paths: "npm:^4.2.0" peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^9.1.16 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.1.9 vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10/5d5e1ab752c2212d5c00d0150f46b1a9ef3d86ac3ee8ec359de301ba7801b0272eadd52a3e647b2d40db72ca1d321c981b8e392ec060d5e1ec39c4a3227c3aa5 + checksum: 10/e55978b8eaee2f5d9a7b30c2547d3f29497468c0bac5f20308633554f381d066d2f0549a4997d9ec29693f018d6889d9b9c4bfb4b2e822c4730999fa1605c573 languageName: node linkType: hard -"@storybook/react@npm:9.1.16": - version: 9.1.16 - resolution: "@storybook/react@npm:9.1.16" +"@storybook/react@npm:10.1.9": + version: 10.1.9 + resolution: "@storybook/react@npm:10.1.9" dependencies: "@storybook/global": "npm:^5.0.0" - "@storybook/react-dom-shim": "npm:9.1.16" + "@storybook/react-dom-shim": "npm:10.1.9" + react-docgen: "npm:^8.0.2" peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^9.1.16 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.1.9 typescript: ">= 4.9.x" peerDependenciesMeta: typescript: optional: true - checksum: 10/ead1396a6fdd3cbb4170fbf1f390073d64a07fb4114243a5a6d7c975d8f35aae0220c0c731c62f5f6be2f6a6a92b2b8b150066da7b463ae848cc43dd4b8670f1 + checksum: 10/964f5a3b77638597289c72be0991323fcbe0f5d7451b5c7bceac7e998dd819897936fb486e38d0b70e42507ca6895850d9a484acc8d171a6ecbd9ce41a0b19ad languageName: node linkType: hard -"@swagger-api/apidom-ast@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-ast@npm:1.0.0-beta.48" +"@swagger-api/apidom-ast@npm:^1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@swagger-api/apidom-ast@npm:1.0.0-rc.4" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-error": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-error": "npm:^1.0.0-rc.4" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" unraw: "npm:^3.0.0" - checksum: 10/b3f2b91df2b9116db318ce44551d3846453b7d9f17eb809c19890ecbbc17166ad6a1fa54698b79e85600960e2f1dd2ea56487d9a50537ee362a1a9dd63f91840 + checksum: 10/77be60bd4085525de506eb31a4f67de7850296534b98cb981db5a9e3ac8ebe07b92cb6ff8edacc5f199373f8dab9bc845b6695ae4a40dc15d0e05a05c8b3041f languageName: node linkType: hard -"@swagger-api/apidom-core@npm:>=1.0.0-beta.41 <1.0.0-rc.0, @swagger-api/apidom-core@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-core@npm:1.0.0-beta.48" +"@swagger-api/apidom-core@npm:^1.0.0-rc.1, @swagger-api/apidom-core@npm:^1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@swagger-api/apidom-core@npm:1.0.0-rc.4" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-ast": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-error": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-ast": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-error": "npm:^1.0.0-rc.4" "@types/ramda": "npm:~0.30.0" minim: "npm:~0.23.8" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" short-unique-id: "npm:^5.3.2" ts-mixer: "npm:^6.0.3" - checksum: 10/abf7328a4d821f6083daa80fe1e719511825ff855dc4be59a4147a100c1ae34254cba4050ecc29e5147877f7e9d797681e96931da0ce432c310aaae82cd5a371 + checksum: 10/73763b2888aa1e7642dd08192fa91a6c5049575856bb6916333bf194f1798706b424e38efd39d9729ce95ec1fe545407c6ec3a9212e360d57af02f003f077643 languageName: node linkType: hard -"@swagger-api/apidom-error@npm:>=1.0.0-beta.41 <1.0.0-rc.0, @swagger-api/apidom-error@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-error@npm:1.0.0-beta.48" +"@swagger-api/apidom-error@npm:^1.0.0-rc.1, @swagger-api/apidom-error@npm:^1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@swagger-api/apidom-error@npm:1.0.0-rc.4" dependencies: "@babel/runtime-corejs3": "npm:^7.20.7" - checksum: 10/ce6461f5b06a6297074949d1aabd605c272c3061241e4d47f3720707ac8bc41c272deed033ae131fb5620bd6d709e0744275353ed490f7eca81f75db94abfc7e + checksum: 10/dfbc2e9d570eb15e925a4eb0dd4e59f8d5a01cfda4a78dd83320c71f3406082b73a6ea2b61229730a3830a707fdb94b17b3b0cb0ffaf45699917434b36971409 languageName: node linkType: hard -"@swagger-api/apidom-json-pointer@npm:>=1.0.0-beta.41 <1.0.0-rc.0, @swagger-api/apidom-json-pointer@npm:^1.0.0-beta.40 <1.0.0-rc.0, @swagger-api/apidom-json-pointer@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-json-pointer@npm:1.0.0-beta.48" +"@swagger-api/apidom-json-pointer@npm:^1.0.0-rc.0, @swagger-api/apidom-json-pointer@npm:^1.0.0-rc.1, @swagger-api/apidom-json-pointer@npm:^1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@swagger-api/apidom-json-pointer@npm:1.0.0-rc.4" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-error": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-error": "npm:^1.0.0-rc.4" "@swaggerexpert/json-pointer": "npm:^2.10.1" - checksum: 10/2075f4d64813128ee80da21f16fd96bc51cf232505d920fc77af86acd2ea1db342936753b824153b232e9522d4a7f389fd870afdf3518b0af91f497119b6e1c5 + checksum: 10/cd8ea73069934422a7c48a3ca6a61201e3acfc35cd8cb8df016c4bcfb655b6ba21abee5011edbe4cc1fa83076e1d1c3031d8e9384b3e0b2e6e0b103fa3ea96cc languageName: node linkType: hard -"@swagger-api/apidom-ns-api-design-systems@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-ns-api-design-systems@npm:1.0.0-beta.48" +"@swagger-api/apidom-ns-api-design-systems@npm:^1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@swagger-api/apidom-ns-api-design-systems@npm:1.0.0-rc.4" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-error": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-openapi-3-1": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-error": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-ns-openapi-3-1": "npm:^1.0.0-rc.4" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.3" - checksum: 10/48e6a4f323572715f8990f83f6a1617af9d7ca0c6cd0e08f9b05c6f178664da233498c7c2461f636a97a525aaa60748c1da2a8d949ed43a7a14c1639d382f737 + checksum: 10/e30c5da34485eeb62be9b37ccc58bc11d2f4559799369a391157c42b2ff5bbb46e9a931b9e0dcd856912ad4078bc2631f6b272464e77709fdfd8c1725c780d3f languageName: node linkType: hard -"@swagger-api/apidom-ns-arazzo-1@npm:^1.0.0-beta.40 <1.0.0-rc.0, @swagger-api/apidom-ns-arazzo-1@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-ns-arazzo-1@npm:1.0.0-beta.48" +"@swagger-api/apidom-ns-arazzo-1@npm:^1.0.0-rc.0, @swagger-api/apidom-ns-arazzo-1@npm:^1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@swagger-api/apidom-ns-arazzo-1@npm:1.0.0-rc.4" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-json-schema-2020-12": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-ns-json-schema-2020-12": "npm:^1.0.0-rc.4" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.3" - checksum: 10/3b9f60ec9ca9dccc5e3d46a48a6d7b30915294bdb7dc6ee481aae57de707485885f887ad9b9daec469712c087e6d74ed748ac8d748ed6d19cb1fb6219141bb12 + checksum: 10/4c56afb584d79e7be530b5aba31cf4556d5fbefeafcd8d25c03fa74d895bafb99e8c9d266c3b09f06758c7bbcff6a1367105240e238cd56312e83e1f8393430c languageName: node linkType: hard -"@swagger-api/apidom-ns-asyncapi-2@npm:^1.0.0-beta.40 <1.0.0-rc.0, @swagger-api/apidom-ns-asyncapi-2@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-ns-asyncapi-2@npm:1.0.0-beta.48" +"@swagger-api/apidom-ns-asyncapi-2@npm:^1.0.0-rc.0, @swagger-api/apidom-ns-asyncapi-2@npm:^1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@swagger-api/apidom-ns-asyncapi-2@npm:1.0.0-rc.4" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-json-schema-draft-7": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-ns-json-schema-draft-7": "npm:^1.0.0-rc.4" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.3" - checksum: 10/99bcd30eb7c14334b2ebe3adfdb7ad8cf93d9565fc72217863a82b1ba48d4b5839b639964cf830bf9ba64c41c7a7d7eedeb53d8d28e5379bde1fafa8b424ab02 + checksum: 10/94ea647db429877c914dc6449165570ea13233d81f9b3b28f6d5d035b7cccdd53f7bf50279ad97b42a91f20d109d50a040e32fa571b1a1f5793e339e4426c6db languageName: node linkType: hard -"@swagger-api/apidom-ns-json-schema-2019-09@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-ns-json-schema-2019-09@npm:1.0.0-beta.48" +"@swagger-api/apidom-ns-json-schema-2019-09@npm:^1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@swagger-api/apidom-ns-json-schema-2019-09@npm:1.0.0-rc.4" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-error": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-json-schema-draft-7": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-error": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-ns-json-schema-draft-7": "npm:^1.0.0-rc.4" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.4" - checksum: 10/bed0ed8b2ef35def9da2b8cebb31dba0d83fab88a38ab1a1af4760d370f5b3d0a1dfda7f1f1991204eab97531ddc90c70c153cb3ba9a7a5b93be93f02d873097 + checksum: 10/8760f7252a31eaed22417f3f181e1f29f25c9100044a78b4f1c3690ddc6402845ba5c90ee98297dc56f7b3d3758c4fb086c53c204b7d1c8f87faebdf57654ea5 languageName: node linkType: hard -"@swagger-api/apidom-ns-json-schema-2020-12@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-ns-json-schema-2020-12@npm:1.0.0-beta.48" +"@swagger-api/apidom-ns-json-schema-2020-12@npm:^1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@swagger-api/apidom-ns-json-schema-2020-12@npm:1.0.0-rc.4" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-error": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-json-schema-2019-09": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-error": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-ns-json-schema-2019-09": "npm:^1.0.0-rc.4" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.4" - checksum: 10/6bfad975a9db33224bc980edafab8e39218d6eb0acc384881ba14b35061d2c51b45f3e778529f5cf5875330501c892f433319dc4613a2d08ae22cf33f832d91e + checksum: 10/05d31abb732435cfbdc6cc46332f4a2d90462e11c103fefa22f669827c99916363bc569adce0fcbbe52c51061c490006f8ca20bd1420ecfea680ad35589f8390 languageName: node linkType: hard -"@swagger-api/apidom-ns-json-schema-draft-4@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-ns-json-schema-draft-4@npm:1.0.0-beta.48" +"@swagger-api/apidom-ns-json-schema-draft-4@npm:^1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@swagger-api/apidom-ns-json-schema-draft-4@npm:1.0.0-rc.4" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-ast": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-ast": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-core": "npm:^1.0.0-rc.4" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.4" - checksum: 10/860c09c46b6ef274afeaf4fb5aa0445dd9d394fb81f3d251d34c484ce31ca568e8d8e63acdfd207180dc28ac1a6fe082fe7139dbe1aadac2e5b81f42f80aefcd + checksum: 10/37223af3cf5e6feff7021e40129eacda2f89f7a176466bfdef12f2785b8963a4583ff8466215af7b2f7508515c29baa86a549ee1449a7b40afee7c84c27f5ec8 languageName: node linkType: hard -"@swagger-api/apidom-ns-json-schema-draft-6@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-ns-json-schema-draft-6@npm:1.0.0-beta.48" +"@swagger-api/apidom-ns-json-schema-draft-6@npm:^1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@swagger-api/apidom-ns-json-schema-draft-6@npm:1.0.0-rc.4" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-error": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-json-schema-draft-4": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-error": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-ns-json-schema-draft-4": "npm:^1.0.0-rc.4" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.4" - checksum: 10/418d8c1cf20cc12841ee1d7e17bc57cbfa296d7833d4f656c650fcb5ade4bd646088177c9cc1c682b2ebd80fb3713de02b8dc562d155e3874c482e9d0d4edcc2 + checksum: 10/9294144d88e1bac74f26c99c3b7b1a0dacdf5e7823360d6ae18918c67a20ae42b297c00b4d641a803bd90ad6c1e42390b4d1ef1de5916becead4fee1e4c0507c languageName: node linkType: hard -"@swagger-api/apidom-ns-json-schema-draft-7@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-ns-json-schema-draft-7@npm:1.0.0-beta.48" +"@swagger-api/apidom-ns-json-schema-draft-7@npm:^1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@swagger-api/apidom-ns-json-schema-draft-7@npm:1.0.0-rc.4" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-error": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-json-schema-draft-6": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-error": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-ns-json-schema-draft-6": "npm:^1.0.0-rc.4" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.4" - checksum: 10/6e683458465f221c490440f94a1d1328a39db87a27c257a5d700b2effc937b8568f98f5042d74f56fdf6692da4b4027074c33c59348199fc821c45de9598e473 + checksum: 10/53111c3c6f3ba7ae21863b70afdd0eeea4db9b571602c9ec79e4525aaaa83dde32907c8a05ce658e5244b4400ff554dc7aca9c73a9a1b91cfffecf0f09bf4f48 languageName: node linkType: hard -"@swagger-api/apidom-ns-openapi-2@npm:^1.0.0-beta.40 <1.0.0-rc.0, @swagger-api/apidom-ns-openapi-2@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-ns-openapi-2@npm:1.0.0-beta.48" +"@swagger-api/apidom-ns-openapi-2@npm:^1.0.0-rc.0, @swagger-api/apidom-ns-openapi-2@npm:^1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@swagger-api/apidom-ns-openapi-2@npm:1.0.0-rc.4" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-error": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-json-schema-draft-4": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-error": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-ns-json-schema-draft-4": "npm:^1.0.0-rc.4" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.3" - checksum: 10/768fb7ec2c8506857837975529f311fe28359dd56a836c3b3cb1685bed623c489b5327e5fe58a41e70235ba525ba6401cc32fc9bd50bc5ec1cc0baf6fe144900 + checksum: 10/42dbedf81c96c2e3e851b234777772e8662cf4957f3d1cc81dac0356a33840652e61011133fd404b0465f3a238451878cdb53182f9d295d53ff6a3e97ed94265 languageName: node linkType: hard -"@swagger-api/apidom-ns-openapi-3-0@npm:^1.0.0-beta.40 <1.0.0-rc.0, @swagger-api/apidom-ns-openapi-3-0@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-ns-openapi-3-0@npm:1.0.0-beta.48" +"@swagger-api/apidom-ns-openapi-3-0@npm:^1.0.0-rc.0, @swagger-api/apidom-ns-openapi-3-0@npm:^1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@swagger-api/apidom-ns-openapi-3-0@npm:1.0.0-rc.4" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-error": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-json-schema-draft-4": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-error": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-ns-json-schema-draft-4": "npm:^1.0.0-rc.4" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.3" - checksum: 10/a005d660c2e476a3d1bb74da9a2efa7fb0a68b1f52ddbde01514f4db9ef14051833f55b001c812da44fce3445c145ce5fd62a137477a71a6bcad220115c1e440 + checksum: 10/91211128aa583a1d155789e4e7e5a5fc7a7adc1fc69f836e5b742168d7f60e2c3a961ab449b5cbf47c803e536cf3490ff4ce44608d9834c2d06eeb9eb0b3feef languageName: node linkType: hard -"@swagger-api/apidom-ns-openapi-3-1@npm:>=1.0.0-beta.41 <1.0.0-rc.0, @swagger-api/apidom-ns-openapi-3-1@npm:^1.0.0-beta.40 <1.0.0-rc.0, @swagger-api/apidom-ns-openapi-3-1@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-ns-openapi-3-1@npm:1.0.0-beta.48" +"@swagger-api/apidom-ns-openapi-3-1@npm:^1.0.0-rc.0, @swagger-api/apidom-ns-openapi-3-1@npm:^1.0.0-rc.1, @swagger-api/apidom-ns-openapi-3-1@npm:^1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@swagger-api/apidom-ns-openapi-3-1@npm:1.0.0-rc.4" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-ast": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-json-pointer": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-json-schema-2020-12": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-openapi-3-0": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-ast": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-core": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-json-pointer": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-ns-json-schema-2020-12": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-ns-openapi-3-0": "npm:^1.0.0-rc.4" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" ts-mixer: "npm:^6.0.3" - checksum: 10/3423b3d642487d562376a572dac31fec760d3a64fe8bb4065ee0634a340c418656b2ea9cfc484059db5105e7389c74ac32620413243990141a7bbdc2e7cb1062 + checksum: 10/11739023a16d5aa41766aa24d094799e41b80644b4129ea3df9d606343abc6215f0350062ccb709d5361b7f2e1f516d0f2fea7c53cd1a938b1d4c2f859ac1a4b languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-api-design-systems-json@npm:^1.0.0-beta.40 <1.0.0-rc.0": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-parser-adapter-api-design-systems-json@npm:1.0.0-beta.48" +"@swagger-api/apidom-parser-adapter-api-design-systems-json@npm:^1.0.0-rc.0": + version: 1.0.0-rc.4 + resolution: "@swagger-api/apidom-parser-adapter-api-design-systems-json@npm:1.0.0-rc.4" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-api-design-systems": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-parser-adapter-json": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-ns-api-design-systems": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-parser-adapter-json": "npm:^1.0.0-rc.4" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/ac86988052a7ad9f03c389cef02b22da36d6cbaae6c670c7642615de7f6efe921b3991f14a55793f803e5b5cb9eb0bcda30857a70a0a0d1c31d8f2d8023e78c6 + checksum: 10/edb734b20658354c865cc6b12315450be0d7ac340fce80185cab8f476ae9ec8983d13042788132f24ff9b32c4edf292d28c1e88a1652b927ec6e6c4679f7cce1 languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-api-design-systems-yaml@npm:^1.0.0-beta.40 <1.0.0-rc.0": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-parser-adapter-api-design-systems-yaml@npm:1.0.0-beta.48" +"@swagger-api/apidom-parser-adapter-api-design-systems-yaml@npm:^1.0.0-rc.0": + version: 1.0.0-rc.4 + resolution: "@swagger-api/apidom-parser-adapter-api-design-systems-yaml@npm:1.0.0-rc.4" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-api-design-systems": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-ns-api-design-systems": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.0.0-rc.4" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/c0c9f35707afe05c559c8edacc0f548d3916f8f0cb0b4800b441258cbd871ba5c8ed083758b82bbe2589f022964bcd199f8d839648a78e979c94234e7ff22a08 + checksum: 10/8477220d192b17eb165096cb8fe699d48b554f2eb0ef737218f7136100a2a3cb7dfbcbf954dab4244796d48d0e82154b0788cfd9fe36226dcc85b2a6e11f65f0 languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-arazzo-json-1@npm:^1.0.0-beta.40 <1.0.0-rc.0": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-parser-adapter-arazzo-json-1@npm:1.0.0-beta.48" +"@swagger-api/apidom-parser-adapter-arazzo-json-1@npm:^1.0.0-rc.0": + version: 1.0.0-rc.4 + resolution: "@swagger-api/apidom-parser-adapter-arazzo-json-1@npm:1.0.0-rc.4" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-arazzo-1": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-parser-adapter-json": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-ns-arazzo-1": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-parser-adapter-json": "npm:^1.0.0-rc.4" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/ec8bd9268b09d3446bb922d881ceab4f52164f995409a305dfa140b9c9790c2f75bf9d59bbc1d6859e1943bc1376ee72a6b7865eaf4b2043795c8576d489ce1b + checksum: 10/545d8883a0c5eedfd99c4a85347355865c701eed114cd75df54965174f07f2f4a884efd61e836d2168bc859dddfbbd168857dd0f3721b0d397f4c424bd0312ec languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-arazzo-yaml-1@npm:^1.0.0-beta.40 <1.0.0-rc.0": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-parser-adapter-arazzo-yaml-1@npm:1.0.0-beta.48" +"@swagger-api/apidom-parser-adapter-arazzo-yaml-1@npm:^1.0.0-rc.0": + version: 1.0.0-rc.4 + resolution: "@swagger-api/apidom-parser-adapter-arazzo-yaml-1@npm:1.0.0-rc.4" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-arazzo-1": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-ns-arazzo-1": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.0.0-rc.4" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/285a55e288475bd20a960fcc90d61bb0b29c8fc586a77ac3ba254c7eacc11db95a964648ae61e8deec20d90bfe32ea5eb11ec8857c6c4b44061c98045be75d5c + checksum: 10/acec60d1874d22990b021b64458f153c080a15239c84f1ed4a710ad7480d2a7c5a8c04cb895ae874e83bf467865abc6e93d30da2f58aee0921fcab375b4565f6 languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-asyncapi-json-2@npm:^1.0.0-beta.40 <1.0.0-rc.0": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-parser-adapter-asyncapi-json-2@npm:1.0.0-beta.48" +"@swagger-api/apidom-parser-adapter-asyncapi-json-2@npm:^1.0.0-rc.0": + version: 1.0.0-rc.4 + resolution: "@swagger-api/apidom-parser-adapter-asyncapi-json-2@npm:1.0.0-rc.4" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-asyncapi-2": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-parser-adapter-json": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-ns-asyncapi-2": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-parser-adapter-json": "npm:^1.0.0-rc.4" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/1011a2be2fd576013ae5b725592c49f99dc42c50127d2707dc48893a33eb94a3820f62be6650f5bcbdb987034b5ef150905415eb44c7384a3376da97982bc467 + checksum: 10/16303fe4e38f993e25ad96d11f28a5b1ff42dd75ca2401b85b17f7937bbdcf7fb645c96644d3a70bf1c79d615609df4526f073c26a7719b5a4c4de50bd21f51e languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-asyncapi-yaml-2@npm:^1.0.0-beta.40 <1.0.0-rc.0": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2@npm:1.0.0-beta.48" +"@swagger-api/apidom-parser-adapter-asyncapi-yaml-2@npm:^1.0.0-rc.0": + version: 1.0.0-rc.4 + resolution: "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2@npm:1.0.0-rc.4" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-asyncapi-2": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-ns-asyncapi-2": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.0.0-rc.4" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/c6f28dc27fe796ef3c8440afaa3ce78163e1290fd3c73437541eb629b028d4552537fdeaf29cfd9415774ee2909321289f5c9eb4682e6c5cbf6981ddabaf78b5 + checksum: 10/0894af80561bf688bc8fa06bf21e19e6b1ad6dbbe7c2c1d8f3e449f63d8d2d6e437647def9ef92ca6044df4d46a75b9581297cae655e1489b50418ac3e8de90b languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-json@npm:^1.0.0-beta.40 <1.0.0-rc.0, @swagger-api/apidom-parser-adapter-json@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-parser-adapter-json@npm:1.0.0-beta.48" +"@swagger-api/apidom-parser-adapter-json@npm:^1.0.0-rc.0, @swagger-api/apidom-parser-adapter-json@npm:^1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@swagger-api/apidom-parser-adapter-json@npm:1.0.0-rc.4" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-ast": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-error": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-ast": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-core": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-error": "npm:^1.0.0-rc.4" "@types/ramda": "npm:~0.30.0" node-gyp: "npm:latest" ramda: "npm:~0.30.0" @@ -19472,148 +19622,147 @@ __metadata: tree-sitter: "npm:=0.21.1" tree-sitter-json: "npm:=0.24.8" web-tree-sitter: "npm:=0.24.5" - checksum: 10/ddb74c62c2a28d359584d385027db17c9630186e6f007e28220379ac27b2875c236b5a748101ff0d46edc46a222287db3ba3e0f43345ce2cf978cda691879b04 + checksum: 10/7447cdc9e19a585e3e7275ae6a560c6ab137c39b5d5548bec4a1499b178d669cd78ed27d4b5f24187e1f09d840b5735ae01b24e6cfe7b0ed9920a587a9b16109 languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-openapi-json-2@npm:^1.0.0-beta.40 <1.0.0-rc.0": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-parser-adapter-openapi-json-2@npm:1.0.0-beta.48" +"@swagger-api/apidom-parser-adapter-openapi-json-2@npm:^1.0.0-rc.0": + version: 1.0.0-rc.4 + resolution: "@swagger-api/apidom-parser-adapter-openapi-json-2@npm:1.0.0-rc.4" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-openapi-2": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-parser-adapter-json": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-ns-openapi-2": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-parser-adapter-json": "npm:^1.0.0-rc.4" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/c5d4ebfa13c58487626a9b2764995f690db5bbe25de80212d9f216451b20716facb67f3fccae03ed986810d5ecb832756716ba86fcd506b1a3e974aa0e3117dc + checksum: 10/85f11179c6ebd7dbd7a99cf969e6c5b67ea0b95d64d8a41b83d87ea8db2ec95e23fd0481bbb365bbfedb5f8d7cdf6c193a8120562d9f104e1395272c52feca74 languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-openapi-json-3-0@npm:^1.0.0-beta.40 <1.0.0-rc.0": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-parser-adapter-openapi-json-3-0@npm:1.0.0-beta.48" +"@swagger-api/apidom-parser-adapter-openapi-json-3-0@npm:^1.0.0-rc.0": + version: 1.0.0-rc.4 + resolution: "@swagger-api/apidom-parser-adapter-openapi-json-3-0@npm:1.0.0-rc.4" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-openapi-3-0": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-parser-adapter-json": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-ns-openapi-3-0": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-parser-adapter-json": "npm:^1.0.0-rc.4" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/b2e2d71cbb862ec477ebdda8f0e71353c58cef40b1d709ab8cdcea364757f7ffd2643c13a8ff60d0993ffe6a9229deaba6654fea6f3a890730a0b598183de1db + checksum: 10/fa701aa34dfb6f8088a8b896ed4ee2dfdfbe0b0bab80914e8aa85594de1babce65cd37256d70bc6b46eaae726d9dcbe0f564fd6a8711c44997729b724c85adbf languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-openapi-json-3-1@npm:^1.0.0-beta.40 <1.0.0-rc.0": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-parser-adapter-openapi-json-3-1@npm:1.0.0-beta.48" +"@swagger-api/apidom-parser-adapter-openapi-json-3-1@npm:^1.0.0-rc.0": + version: 1.0.0-rc.4 + resolution: "@swagger-api/apidom-parser-adapter-openapi-json-3-1@npm:1.0.0-rc.4" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-openapi-3-1": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-parser-adapter-json": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-ns-openapi-3-1": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-parser-adapter-json": "npm:^1.0.0-rc.4" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/a9db188dee99f38e0a91644362e87c526559ddfb35019ed69738a5bebdad645beab925f47d1f0ae2430ca10bd83f329a4007bb2b3b6d23c6fe32d23865fdd672 + checksum: 10/382a36f76315862ceeb15b4e16e01f3e8aaf9b846025653c6a8a81230e8ac05a93ae1f9f1ce1fea76ca7ee2a8c5f0577f33ae8913cfb02bac23fc74850c47d05 languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-openapi-yaml-2@npm:^1.0.0-beta.40 <1.0.0-rc.0": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-parser-adapter-openapi-yaml-2@npm:1.0.0-beta.48" +"@swagger-api/apidom-parser-adapter-openapi-yaml-2@npm:^1.0.0-rc.0": + version: 1.0.0-rc.4 + resolution: "@swagger-api/apidom-parser-adapter-openapi-yaml-2@npm:1.0.0-rc.4" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-openapi-2": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-ns-openapi-2": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.0.0-rc.4" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/ed29926c1b08108cae294bc100fd279b555731ebc23edba2461e4817f82273003cf650f1eac0d1f2382ad590d76255e23ebf08438c3017ddcec7149e7140761d + checksum: 10/8236aea1dabe5d968a0111a196e7799ad20e3957583bf6f01bb63008ff8a0aa33db457133327be0db4d872a6a844e9cfc4c1962d3b23090a3588a0498ccec3ee languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-openapi-yaml-3-0@npm:^1.0.0-beta.40 <1.0.0-rc.0": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0@npm:1.0.0-beta.48" +"@swagger-api/apidom-parser-adapter-openapi-yaml-3-0@npm:^1.0.0-rc.0": + version: 1.0.0-rc.4 + resolution: "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0@npm:1.0.0-rc.4" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-openapi-3-0": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-ns-openapi-3-0": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.0.0-rc.4" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/10d28a60ec9b4197d0c75bf05832c07fc1e8f61f72875ebe1baf328671c53499b51a9c065e1f0310c7c22aa6cb8f51ab2e8ba2692eee2b52f609982e8fe316c5 + checksum: 10/5286670c5c7b1fad5e97f0da88e40870ba98e2511334eb32c99d9b8d8059e6cc77b2ca6f3e539a9db2cd75809d2695c4000288f14a00d00a38107360ee85825c languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-openapi-yaml-3-1@npm:^1.0.0-beta.40 <1.0.0-rc.0": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1@npm:1.0.0-beta.48" +"@swagger-api/apidom-parser-adapter-openapi-yaml-3-1@npm:^1.0.0-rc.0": + version: 1.0.0-rc.4 + resolution: "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1@npm:1.0.0-rc.4" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-ns-openapi-3-1": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-core": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-ns-openapi-3-1": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.0.0-rc.4" "@types/ramda": "npm:~0.30.0" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" - checksum: 10/2f31d09f7aba46b94ad2891097c3e2b880f6f76fab298dbcd794dd42c49fc0d9194f5a73abeb4f3140c4b53deff769f3eddbf16e70a9d764c36cdb24b4998071 + checksum: 10/bdce51f49c2784217237e667ddc4c111095ed7dd11cef94b3383ddf6a4a0e886492546a301aa803fdcfc7fb9923f5cdcfaba305cc98fcfeedd02d85e749a80f8 languageName: node linkType: hard -"@swagger-api/apidom-parser-adapter-yaml-1-2@npm:^1.0.0-beta.40 <1.0.0-rc.0, @swagger-api/apidom-parser-adapter-yaml-1-2@npm:^1.0.0-beta.48": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-parser-adapter-yaml-1-2@npm:1.0.0-beta.48" +"@swagger-api/apidom-parser-adapter-yaml-1-2@npm:^1.0.0-rc.0, @swagger-api/apidom-parser-adapter-yaml-1-2@npm:^1.0.0-rc.4": + version: 1.0.0-rc.4 + resolution: "@swagger-api/apidom-parser-adapter-yaml-1-2@npm:1.0.0-rc.4" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-ast": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-error": "npm:^1.0.0-beta.48" + "@swagger-api/apidom-ast": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-core": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-error": "npm:^1.0.0-rc.4" "@tree-sitter-grammars/tree-sitter-yaml": "npm:=0.7.1" "@types/ramda": "npm:~0.30.0" - node-gyp: "npm:latest" ramda: "npm:~0.30.0" ramda-adjunct: "npm:^5.0.0" tree-sitter: "npm:=0.22.4" web-tree-sitter: "npm:=0.24.5" - checksum: 10/c032b088ffef50e119fb9cb60e7a6edfc17ec09e40c6d5ee62f5561c4da806f29ef5ba850f701763c755491f323754c23bb5a07da0cbbd3ed7d673caf5364559 + checksum: 10/c47bf36c8c2cbb8e2a0004167199d1a79df2c9c3cb97e50d7e55c2aa869546ad4376e067593b045a71d92df708b96bdadc0e7878dc60e547b9345e54384fb096 languageName: node linkType: hard -"@swagger-api/apidom-reference@npm:>=1.0.0-beta.41 <1.0.0-rc.0": - version: 1.0.0-beta.48 - resolution: "@swagger-api/apidom-reference@npm:1.0.0-beta.48" +"@swagger-api/apidom-reference@npm:^1.0.0-rc.1": + version: 1.0.0-rc.4 + resolution: "@swagger-api/apidom-reference@npm:1.0.0-rc.4" dependencies: "@babel/runtime-corejs3": "npm:^7.26.10" - "@swagger-api/apidom-core": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-error": "npm:^1.0.0-beta.48" - "@swagger-api/apidom-json-pointer": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-ns-arazzo-1": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-ns-asyncapi-2": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-ns-openapi-2": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-ns-openapi-3-0": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-ns-openapi-3-1": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-parser-adapter-api-design-systems-json": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-parser-adapter-arazzo-json-1": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-parser-adapter-arazzo-yaml-1": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-parser-adapter-json": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-parser-adapter-openapi-json-2": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-parser-adapter-openapi-yaml-2": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "npm:^1.0.0-beta.40 <1.0.0-rc.0" - "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.0.0-beta.40 <1.0.0-rc.0" + "@swagger-api/apidom-core": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-error": "npm:^1.0.0-rc.4" + "@swagger-api/apidom-json-pointer": "npm:^1.0.0-rc.0" + "@swagger-api/apidom-ns-arazzo-1": "npm:^1.0.0-rc.0" + "@swagger-api/apidom-ns-asyncapi-2": "npm:^1.0.0-rc.0" + "@swagger-api/apidom-ns-openapi-2": "npm:^1.0.0-rc.0" + "@swagger-api/apidom-ns-openapi-3-0": "npm:^1.0.0-rc.0" + "@swagger-api/apidom-ns-openapi-3-1": "npm:^1.0.0-rc.0" + "@swagger-api/apidom-parser-adapter-api-design-systems-json": "npm:^1.0.0-rc.0" + "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "npm:^1.0.0-rc.0" + "@swagger-api/apidom-parser-adapter-arazzo-json-1": "npm:^1.0.0-rc.0" + "@swagger-api/apidom-parser-adapter-arazzo-yaml-1": "npm:^1.0.0-rc.0" + "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "npm:^1.0.0-rc.0" + "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "npm:^1.0.0-rc.0" + "@swagger-api/apidom-parser-adapter-json": "npm:^1.0.0-rc.0" + "@swagger-api/apidom-parser-adapter-openapi-json-2": "npm:^1.0.0-rc.0" + "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "npm:^1.0.0-rc.0" + "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "npm:^1.0.0-rc.0" + "@swagger-api/apidom-parser-adapter-openapi-yaml-2": "npm:^1.0.0-rc.0" + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "npm:^1.0.0-rc.0" + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "npm:^1.0.0-rc.0" + "@swagger-api/apidom-parser-adapter-yaml-1-2": "npm:^1.0.0-rc.0" "@types/ramda": "npm:~0.30.0" - axios: "npm:^1.9.0" + axios: "npm:^1.12.2" minimatch: "npm:^7.4.3" process: "npm:^0.11.10" ramda: "npm:~0.30.0" @@ -19659,7 +19808,7 @@ __metadata: optional: true "@swagger-api/apidom-parser-adapter-yaml-1-2": optional: true - checksum: 10/fd86401690a3992123783c1b6044cdbb4a36b75d437e2519f6ec092d1b03273be45e884fc426e27a9413484047d40f819751f4f7174d9625de8a52a28d2d9e35 + checksum: 10/0efab87df6254abb4259912e6f845420b9fbd33c6a0491fdba5c166f0fcee880bdcea898e03b92b20bb5bf6251047df986797f6471000633a7dd448968e1de50 languageName: node linkType: hard @@ -19681,92 +19830,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.6": + version: 1.15.6 + resolution: "@swc/core-darwin-arm64@npm:1.15.6" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-darwin-x64@npm:1.11.24" +"@swc/core-darwin-x64@npm:1.15.6": + version: 1.15.6 + resolution: "@swc/core-darwin-x64@npm:1.15.6" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.11.24" +"@swc/core-linux-arm-gnueabihf@npm:1.15.6": + version: 1.15.6 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.15.6" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-linux-arm64-gnu@npm:1.11.24" +"@swc/core-linux-arm64-gnu@npm:1.15.6": + version: 1.15.6 + resolution: "@swc/core-linux-arm64-gnu@npm:1.15.6" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-linux-arm64-musl@npm:1.11.24" +"@swc/core-linux-arm64-musl@npm:1.15.6": + version: 1.15.6 + resolution: "@swc/core-linux-arm64-musl@npm:1.15.6" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-linux-x64-gnu@npm:1.11.24" +"@swc/core-linux-x64-gnu@npm:1.15.6": + version: 1.15.6 + resolution: "@swc/core-linux-x64-gnu@npm:1.15.6" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-linux-x64-musl@npm:1.11.24" +"@swc/core-linux-x64-musl@npm:1.15.6": + version: 1.15.6 + resolution: "@swc/core-linux-x64-musl@npm:1.15.6" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-win32-arm64-msvc@npm:1.11.24" +"@swc/core-win32-arm64-msvc@npm:1.15.6": + version: 1.15.6 + resolution: "@swc/core-win32-arm64-msvc@npm:1.15.6" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-win32-ia32-msvc@npm:1.11.24" +"@swc/core-win32-ia32-msvc@npm:1.15.6": + version: 1.15.6 + resolution: "@swc/core-win32-ia32-msvc@npm:1.15.6" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.11.24": - version: 1.11.24 - resolution: "@swc/core-win32-x64-msvc@npm:1.11.24" +"@swc/core-win32-x64-msvc@npm:1.15.6": + version: 1.15.6 + resolution: "@swc/core-win32-x64-msvc@npm:1.15.6" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@swc/core@npm:^1.3.46": - version: 1.11.24 - resolution: "@swc/core@npm:1.11.24" +"@swc/core@npm:^1.15.6": + version: 1.15.6 + resolution: "@swc/core@npm:1.15.6" 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.6" + "@swc/core-darwin-x64": "npm:1.15.6" + "@swc/core-linux-arm-gnueabihf": "npm:1.15.6" + "@swc/core-linux-arm64-gnu": "npm:1.15.6" + "@swc/core-linux-arm64-musl": "npm:1.15.6" + "@swc/core-linux-x64-gnu": "npm:1.15.6" + "@swc/core-linux-x64-musl": "npm:1.15.6" + "@swc/core-win32-arm64-msvc": "npm:1.15.6" + "@swc/core-win32-ia32-msvc": "npm:1.15.6" + "@swc/core-win32-x64-msvc": "npm:1.15.6" "@swc/counter": "npm:^0.1.3" - "@swc/types": "npm:^0.1.21" + "@swc/types": "npm:^0.1.25" peerDependencies: "@swc/helpers": ">=0.5.17" dependenciesMeta: @@ -19793,7 +19942,7 @@ __metadata: peerDependenciesMeta: "@swc/helpers": optional: true - checksum: 10/0b3e883f8a5652a7ab221a777386ccc8a65fc5b53d533bad15b703b22984eb3b449efd907b1872263f1a9990a9a50612f3c6deb619894a43f03cd974ec9bd1b7 + checksum: 10/d17d1b5e6e81f8c262b668f237b863dd612eaa19fc35e74067b2a27ba3998c0c511ec7f49cd91c02f61fa77dd1b5d7e65d293736f65ec45d6c13989904b1c7eb languageName: node linkType: hard @@ -19804,7 +19953,7 @@ __metadata: languageName: node linkType: hard -"@swc/helpers@npm:^0.5.0, @swc/helpers@npm:^0.5.8": +"@swc/helpers@npm:^0.5.0, @swc/helpers@npm:^0.5.17, @swc/helpers@npm:^0.5.8": version: 0.5.17 resolution: "@swc/helpers@npm:0.5.17" dependencies: @@ -19813,25 +19962,25 @@ __metadata: languageName: node linkType: hard -"@swc/jest@npm:^0.2.22": - version: 0.2.38 - resolution: "@swc/jest@npm:0.2.38" +"@swc/jest@npm:^0.2.39": + version: 0.2.39 + resolution: "@swc/jest@npm:0.2.39" dependencies: - "@jest/create-cache-key-function": "npm:^29.7.0" + "@jest/create-cache-key-function": "npm:^30.0.0" "@swc/counter": "npm:^0.1.3" jsonc-parser: "npm:^3.2.0" peerDependencies: "@swc/core": "*" - checksum: 10/3aaf557425e806890ebefea35334b7795e9f8ddf6f82d634d865ef917333cca4208190af1a9610c134c0e3b7a6a1aea4ec77a659e3ca5965be7aace65ce80c97 + checksum: 10/a2b7ed6fbb908867e673d1bbff9efde7eee225a57ad75735216ce2005e40c5cfb92285bd807d2058f1c0317e3d48ed71f5577fe85b28bebc80c1bc2c3a03306e 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 @@ -19885,7 +20034,7 @@ __metadata: "@types/commander": "npm:^2.12.2" "@types/fs-extra": "npm:^11.0.0" "@types/http-proxy": "npm:^1.17.4" - "@types/node": "npm:^20.16.0" + "@types/node": "npm:^22.13.14" "@types/serve-handler": "npm:^6.1.0" "@types/webpack-env": "npm:^1.15.3" commander: "npm:^12.0.0" @@ -20149,7 +20298,7 @@ __metadata: languageName: node linkType: hard -"@tybys/wasm-util@npm:^0.10.1": +"@tybys/wasm-util@npm:^0.10.0, @tybys/wasm-util@npm:^0.10.1": version: 0.10.1 resolution: "@tybys/wasm-util@npm:0.10.1" dependencies: @@ -20167,12 +20316,12 @@ __metadata: languageName: node linkType: hard -"@types/archiver@npm:^6.0.0": - version: 6.0.4 - resolution: "@types/archiver@npm:6.0.4" +"@types/archiver@npm:^7.0.0": + version: 7.0.0 + resolution: "@types/archiver@npm:7.0.0" dependencies: "@types/readdir-glob": "npm:*" - checksum: 10/93e500f55e3fbb200b10988dbd8407df78e8a3d921d09d97c114bde609ef75e478a2fdeddc1b8da465f569219dfb1fb7dc1ff83332d77c2a03806d68a027c909 + checksum: 10/ed9d2d259b3aa86c64c8b28cfffc01ee8199ff5db906b70ad500233cef032a22368ac456c19aa5ae7b9472e1ec587d834d46b4e85d45a3f1f16bf4c7060c5bc4 languageName: node linkType: hard @@ -20199,10 +20348,10 @@ __metadata: languageName: node linkType: hard -"@types/aws-lambda@npm:8.10.150, @types/aws-lambda@npm:^8.10.83": - version: 8.10.150 - resolution: "@types/aws-lambda@npm:8.10.150" - checksum: 10/d837ae0b94fb16c7cfec3711126f0e7fb109fc3e6d14d994d083d2039c4f2bc52a75f256ee31a8010f023ce78ddcf3746ad43d552592a0f56720167f6d3111be +"@types/aws-lambda@npm:^8.10.155, @types/aws-lambda@npm:^8.10.83": + version: 8.10.159 + resolution: "@types/aws-lambda@npm:8.10.159" + checksum: 10/3fb4d2f5613e3526725676787770442df2cace0d4d4c528c17e791ccee3cc8626447529ed86d937aa709c82db53670835dcf6e0c704d6e3ae92832dcc886eac0 languageName: node linkType: hard @@ -20215,7 +20364,7 @@ __metadata: languageName: node linkType: hard -"@types/babel__core@npm:^7.1.14, @types/babel__core@npm:^7.20.5": +"@types/babel__core@npm:^7.20.5": version: 7.20.5 resolution: "@types/babel__core@npm:7.20.5" dependencies: @@ -20247,7 +20396,7 @@ __metadata: languageName: node linkType: hard -"@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.0.6, @types/babel__traverse@npm:^7.20.7": +"@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.20.7": version: 7.28.0 resolution: "@types/babel__traverse@npm:7.28.0" dependencies: @@ -20555,14 +20704,14 @@ __metadata: languageName: node linkType: hard -"@types/dockerode@npm:^3.3.29": - version: 3.3.38 - resolution: "@types/dockerode@npm:3.3.38" +"@types/dockerode@npm:^3.3.47": + version: 3.3.47 + resolution: "@types/dockerode@npm:3.3.47" dependencies: "@types/docker-modem": "npm:*" "@types/node": "npm:*" "@types/ssh2": "npm:*" - checksum: 10/439a2b905c44bac063ba707f01a35594eb1e44aba8d22d4a8029fd2c5e192d70eae1efe270f85633f4b178fe4223419e14d81b1227a5a6e1ed00fca48c8495dd + checksum: 10/b840ae7872398a3b02e5789006a69d0cf5bb7ec6c0eb714c7ca04ca093add8de4cd06204ecd8f01388e347e62927cf4c599e8b7dba53e81c1350910da766d517 languageName: node linkType: hard @@ -20625,7 +20774,7 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:*, @types/estree@npm:1.0.8, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.5, @types/estree@npm:^1.0.6, @types/estree@npm:^1.0.8": +"@types/estree@npm:*, @types/estree@npm:1.0.8, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.5, @types/estree@npm:^1.0.8": version: 1.0.8 resolution: "@types/estree@npm:1.0.8" checksum: 10/25a4c16a6752538ffde2826c2cc0c6491d90e69cd6187bef4a006dd2c3c45469f049e643d7e516c515f21484dc3d48fd5c870be158a5beb72f5baf3dc43e4099 @@ -20739,15 +20888,6 @@ __metadata: languageName: node linkType: hard -"@types/graceful-fs@npm:^4.1.3": - version: 4.1.5 - resolution: "@types/graceful-fs@npm:4.1.5" - dependencies: - "@types/node": "npm:*" - checksum: 10/d076bb61f45d0fc42dee496ef8b1c2f8742e15d5e47e90e20d0243386e426c04d4efd408a48875ab432f7960b4ce3414db20ed0fbbfc7bcc89d84e574f6e045a - languageName: node - linkType: hard - "@types/hast@npm:^2.0.0": version: 2.3.4 resolution: "@types/hast@npm:2.3.4" @@ -20757,7 +20897,7 @@ __metadata: languageName: node linkType: hard -"@types/hast@npm:^3.0.4": +"@types/hast@npm:^3.0.0, @types/hast@npm:^3.0.4": version: 3.0.4 resolution: "@types/hast@npm:3.0.4" dependencies: @@ -20857,10 +20997,10 @@ __metadata: languageName: node linkType: hard -"@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1": - version: 2.0.1 - resolution: "@types/istanbul-lib-coverage@npm:2.0.1" - checksum: 10/7de11cd954a764985722baddbc11da59109647d800455a7593d696b7763a867c9f1aa2777724cfa493058769617fe09bba90a813086dc322dd540f55fb8207d9 +"@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1, @types/istanbul-lib-coverage@npm:^2.0.6": + version: 2.0.6 + resolution: "@types/istanbul-lib-coverage@npm:2.0.6" + checksum: 10/3feac423fd3e5449485afac999dcfcb3d44a37c830af898b689fadc65d26526460bedb889db278e0d4d815a670331796494d073a10ee6e3a6526301fe7415778 languageName: node linkType: hard @@ -20873,22 +21013,22 @@ __metadata: languageName: node linkType: hard -"@types/istanbul-reports@npm:^3.0.0": - version: 3.0.0 - resolution: "@types/istanbul-reports@npm:3.0.0" +"@types/istanbul-reports@npm:^3.0.0, @types/istanbul-reports@npm:^3.0.4": + version: 3.0.4 + resolution: "@types/istanbul-reports@npm:3.0.4" dependencies: "@types/istanbul-lib-report": "npm:*" - checksum: 10/286a18cff19c4dac4321b9ea406a3560faf577fb2a4df5abf9d577fa81ba831c9baa7d40d03f1daf7fe613d468546b731c00b844b72fad9834c583311a35bb7b + checksum: 10/93eb18835770b3431f68ae9ac1ca91741ab85f7606f310a34b3586b5a34450ec038c3eed7ab19266635499594de52ff73723a54a72a75b9f7d6a956f01edee95 languageName: node linkType: hard -"@types/jest@npm:*, @types/jest@npm:^29.5.11": - version: 29.5.14 - resolution: "@types/jest@npm:29.5.14" +"@types/jest@npm:*, @types/jest@npm:^30.0.0": + version: 30.0.0 + resolution: "@types/jest@npm:30.0.0" dependencies: - expect: "npm:^29.0.0" - pretty-format: "npm:^29.0.0" - checksum: 10/59ec7a9c4688aae8ee529316c43853468b6034f453d08a2e1064b281af9c81234cec986be796288f1bbb29efe943bc950e70c8fa8faae1e460d50e3cf9760f9b + expect: "npm:^30.0.0" + pretty-format: "npm:^30.0.0" + checksum: 10/cdeaa924c68b5233d9ff92861a89e7042df2b0f197633729bcf3a31e65bd4e9426e751c5665b5ac2de0b222b33f100a5502da22aefce3d2c62931c715e88f209 languageName: node linkType: hard @@ -20932,14 +21072,14 @@ __metadata: languageName: node linkType: hard -"@types/jsdom@npm:^20.0.0": - version: 20.0.0 - resolution: "@types/jsdom@npm:20.0.0" +"@types/jsdom@npm:^21.1.7": + version: 21.1.7 + resolution: "@types/jsdom@npm:21.1.7" dependencies: "@types/node": "npm:*" "@types/tough-cookie": "npm:*" parse5: "npm:^7.0.0" - checksum: 10/4cd814a948aa9b739652cde183acc6a9a10bcf04ed82c3a53f691bbc52d156b49269fa3046c70842680a29d240607fcd7be5f35a73ddbf571d95a664e8e49cec + checksum: 10/a5ee54aec813ac928ef783f69828213af4d81325f584e1fe7573a9ae139924c40768d1d5249237e62d51b9a34ed06bde059c86c6b0248d627457ec5e5d532dfa languageName: node linkType: hard @@ -21019,9 +21159,9 @@ __metadata: linkType: hard "@types/lodash@npm:^4.14.151": - version: 4.17.20 - resolution: "@types/lodash@npm:4.17.20" - checksum: 10/8cd8ad3bd78d2e06a93ae8d6c9907981d5673655fec7cb274a4d9a59549aab5bb5b3017361280773b8990ddfccf363e14d1b37c97af8a9fe363de677f9a61524 + version: 4.17.21 + resolution: "@types/lodash@npm:4.17.21" + checksum: 10/34920830a3bc82ba619cda05e606fef00c148a69b4f19f770645d2587ccdb8e42ef3ddfc174b7884c0c709fc0a1aeb48f7326da969bad12a1464a03efbbe414c languageName: node linkType: hard @@ -21039,7 +21179,14 @@ __metadata: languageName: node linkType: hard -"@types/luxon@npm:^3.0.0, @types/luxon@npm:~3.4.0": +"@types/luxon@npm:^3.0.0": + version: 3.7.1 + resolution: "@types/luxon@npm:3.7.1" + checksum: 10/c7bc164c278393ea0be938f986c74b4cddfab9013b1aff4495b016f771ded1d5b7b7b4825b2c7f0b8799edce19c5f531c28ff434ab3dedf994ac2d99a20fd4c4 + languageName: node + linkType: hard + +"@types/luxon@npm:~3.4.0": version: 3.4.2 resolution: "@types/luxon@npm:3.4.2" checksum: 10/fd89566e3026559f2bc4ddcc1e70a2c16161905ed50be9473ec0cfbbbe919165041408c4f6e06c4bcf095445535052e2c099087c76b1b38e368127e618fc968d @@ -21136,12 +21283,12 @@ __metadata: languageName: node linkType: hard -"@types/multer@npm:^1.4.12": - version: 1.4.12 - resolution: "@types/multer@npm:1.4.12" +"@types/multer@npm:^1.4.13": + version: 1.4.13 + resolution: "@types/multer@npm:1.4.13" dependencies: "@types/express": "npm:*" - checksum: 10/3d2b32da58ddd67f972d4ef1021492f78d65f33f936b6fb25dd461bb6cc7b03bfd1de1a11562c4310680dac8054e4398038db51767a0ffbf1fe62457b3706e95 + checksum: 10/f650a9f431007f05a5c51d6359140bea5f77d1299668d45a0791ddbf7789f1c220a3e038039c5e0de6608e273d5ee8001e9649c606d7e0e847e8774ccc5d1d68 languageName: node linkType: hard @@ -21229,21 +21376,22 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^20.16.0": - version: 20.17.30 - resolution: "@types/node@npm:20.17.30" +"@types/node@npm:^22.13.14": + version: 22.19.1 + resolution: "@types/node@npm:22.19.1" dependencies: - undici-types: "npm:~6.19.2" - checksum: 10/69fd3b177417be77b459e8f1dd4e78c85c686167086920fbf35a9fda301709bbeee6a87ad2591fb1ddd96c65e725ec6bb527a06496626a1c94367d1361048f8d + undici-types: "npm:~6.21.0" + checksum: 10/40d5368faa6d9be6c27ebca2362734bc9e035a742e0b5cafee40ba3b355d7cfcaedbc93618c76465451e53f1af0c811b4b85ee9b85e2e942f34a4c5310fa047b languageName: node linkType: hard -"@types/nodemailer@npm:^6.4.14": - version: 6.4.17 - resolution: "@types/nodemailer@npm:6.4.17" +"@types/nodemailer@npm:^7.0.0": + version: 7.0.4 + resolution: "@types/nodemailer@npm:7.0.4" dependencies: + "@aws-sdk/client-sesv2": "npm:^3.839.0" "@types/node": "npm:*" - checksum: 10/bd090c9a81f15ee5e1e2123de1004593bacc24d385460dd56c51ec657d61dc1cfd4f44fc71baac060a1abcb487aef5027509e0afd646e7118d7a8a13a95bad9d + checksum: 10/dbba903cd64d6501b2dfba9ea268b71996dcff3dba24e839385954a4462ae6515d53afc93e8eff3a5b657e5319acdfb6dcd85c10a3216bacbc3c08fabdcf4125 languageName: node linkType: hard @@ -21363,14 +21511,14 @@ __metadata: languageName: node linkType: hard -"@types/pg@npm:*, @types/pg@npm:8.15.4": - version: 8.15.4 - resolution: "@types/pg@npm:8.15.4" +"@types/pg@npm:*, @types/pg@npm:8.15.6": + version: 8.15.6 + resolution: "@types/pg@npm:8.15.6" dependencies: "@types/node": "npm:*" pg-protocol: "npm:*" pg-types: "npm:^2.2.0" - checksum: 10/dd9203ae6732acad4892513fc99eb2bc699935a95b62e9fdbdcc6d1a90f63881b5fd89d4cac1729790ab3d0bb7c64130b144c65abef34e6bbed063ff43a739a6 + checksum: 10/4bc1bb274e0fc105be93e3a9cc8c9aa57fc50b78ed78a56348468157332daaecd71fcab762ee620c766510ffbc7018b56ca394787d6d41ff1726b152770aa532 languageName: node linkType: hard @@ -21395,6 +21543,13 @@ __metadata: languageName: node linkType: hard +"@types/prismjs@npm:^1.0.0": + version: 1.26.5 + resolution: "@types/prismjs@npm:1.26.5" + checksum: 10/617099479db9550119d0f84272dc79d64b2cf3e0d7a17167fe740d55fdf0f155697d935409464392d164e62080c2c88d649cf4bc4fdd30a87127337536657277 + languageName: node + linkType: hard + "@types/promise.allsettled@npm:^1.0.3": version: 1.0.6 resolution: "@types/promise.allsettled@npm:1.0.6" @@ -21451,11 +21606,11 @@ __metadata: linkType: hard "@types/react-grid-layout@npm:^1.3.2": - version: 1.3.5 - resolution: "@types/react-grid-layout@npm:1.3.5" + version: 1.3.6 + resolution: "@types/react-grid-layout@npm:1.3.6" dependencies: "@types/react": "npm:*" - checksum: 10/21599054dfa977ed8445b1ab3198a842531cb36bd620564c3f8a469688cbea051149eb644a99b2f8f6d02f8d9909a6145668f385781b5647601e9663de216946 + checksum: 10/80b9cd6ab14f5cd32a62451544cbbeff32d6c8c3fefd6e04f974429a0b1b0d2aded1f4767ae4ce9cd4caa395e514531f5e5d6a3c3e93dba4906a907f8d77ee21 languageName: node linkType: hard @@ -21698,13 +21853,6 @@ __metadata: languageName: node linkType: hard -"@types/shimmer@npm:^1.2.0": - version: 1.2.0 - resolution: "@types/shimmer@npm:1.2.0" - checksum: 10/f081a31d826ce7bfe8cc7ba8129d2b1dffae44fd580eba4fcf741237646c4c2494ae6de2cada4b7713d138f35f4bc512dbf01311d813dee82020f97d7d8c491c - languageName: node - linkType: hard - "@types/sinon@npm:^17.0.3": version: 17.0.3 resolution: "@types/sinon@npm:17.0.3" @@ -21763,10 +21911,10 @@ __metadata: languageName: node linkType: hard -"@types/stack-utils@npm:^2.0.0": - version: 2.0.0 - resolution: "@types/stack-utils@npm:2.0.0" - checksum: 10/b3fbae25b073116977ecb5c67d22f14567b51a7792403b0bf46e5de8f29bde3bd4ec1626afb22065495ca7f1c699c8bd66720050c94b8f8f9bcefbee79d161fd +"@types/stack-utils@npm:^2.0.3": + version: 2.0.3 + resolution: "@types/stack-utils@npm:2.0.3" + checksum: 10/72576cc1522090fe497337c2b99d9838e320659ac57fa5560fcbdcbafcf5d0216c6b3a0a8a4ee4fdb3b1f5e3420aa4f6223ab57b82fef3578bec3206425c6cf5 languageName: node linkType: hard @@ -21998,12 +22146,12 @@ __metadata: languageName: node linkType: hard -"@types/yargs@npm:^17.0.8": - version: 17.0.12 - resolution: "@types/yargs@npm:17.0.12" +"@types/yargs@npm:^17.0.33, @types/yargs@npm:^17.0.8": + version: 17.0.35 + resolution: "@types/yargs@npm:17.0.35" dependencies: "@types/yargs-parser": "npm:*" - checksum: 10/ffbbfad0c75cc058e0518f202e3651b9fb60c7c1325240cc72ac0a022da746a759ba3c6e0099152076fed5012fb694eb4685e4520c04dc8399c22bb81221bff5 + checksum: 10/47bcd4476a4194ea11617ea71cba8a1eddf5505fc39c44336c1a08d452a0de4486aedbc13f47a017c8efbcb5a8aa358d976880663732ebcbc6dbcbbecadb0581 languageName: node linkType: hard @@ -22038,52 +22186,52 @@ __metadata: linkType: hard "@typescript-eslint/eslint-plugin@npm:^8.17.0": - version: 8.29.1 - resolution: "@typescript-eslint/eslint-plugin@npm:8.29.1" + version: 8.48.1 + resolution: "@typescript-eslint/eslint-plugin@npm:8.48.1" dependencies: "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:8.29.1" - "@typescript-eslint/type-utils": "npm:8.29.1" - "@typescript-eslint/utils": "npm:8.29.1" - "@typescript-eslint/visitor-keys": "npm:8.29.1" + "@typescript-eslint/scope-manager": "npm:8.48.1" + "@typescript-eslint/type-utils": "npm:8.48.1" + "@typescript-eslint/utils": "npm:8.48.1" + "@typescript-eslint/visitor-keys": "npm:8.48.1" graphemer: "npm:^1.4.0" - ignore: "npm:^5.3.1" + ignore: "npm:^7.0.0" natural-compare: "npm:^1.4.0" - ts-api-utils: "npm:^2.0.1" + ts-api-utils: "npm:^2.1.0" peerDependencies: - "@typescript-eslint/parser": ^8.0.0 || ^8.0.0-alpha.0 + "@typescript-eslint/parser": ^8.48.1 eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10/0568894f0ea50e67622605eb347d4e26a41571ad06234478ac695a097e9b3ff6252d6d60034853d7a6b8ec644b5c1d354be21075d691173dd7f2fbecb1102674 + typescript: ">=4.8.4 <6.0.0" + checksum: 10/3ccf420805fb8adb2f3059fa26eb9c6211c0624966d8c8654a1bd586bf87f30be0c62524dfd785185ef573bedd91c42ec3c98c23aed5d60cb9ac583dd9334bc8 languageName: node linkType: hard "@typescript-eslint/parser@npm:^8.16.0": - version: 8.29.1 - resolution: "@typescript-eslint/parser@npm:8.29.1" + version: 8.48.1 + resolution: "@typescript-eslint/parser@npm:8.48.1" dependencies: - "@typescript-eslint/scope-manager": "npm:8.29.1" - "@typescript-eslint/types": "npm:8.29.1" - "@typescript-eslint/typescript-estree": "npm:8.29.1" - "@typescript-eslint/visitor-keys": "npm:8.29.1" + "@typescript-eslint/scope-manager": "npm:8.48.1" + "@typescript-eslint/types": "npm:8.48.1" + "@typescript-eslint/typescript-estree": "npm:8.48.1" + "@typescript-eslint/visitor-keys": "npm:8.48.1" debug: "npm:^4.3.4" peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10/effb4cc24e375e4229e711b3ea8611a205bf81964cb487f518dd4a7d6cecde7324201ce4e2c3cd420e0ce7649899294307da2cd77f145af4efef3a0292189f20 + typescript: ">=4.8.4 <6.0.0" + checksum: 10/d8409c9ede4b1cd2ad0e10e94bb00c54f79352f7d54c97bf24419cb983c19b9f6097e6c31b217ce7ec5cfc9a48117e732d9f88ce0cb8c0ccf7fc3faecdf854a3 languageName: node linkType: hard -"@typescript-eslint/project-service@npm:8.35.0": - version: 8.35.0 - resolution: "@typescript-eslint/project-service@npm:8.35.0" +"@typescript-eslint/project-service@npm:8.48.1": + version: 8.48.1 + resolution: "@typescript-eslint/project-service@npm:8.48.1" dependencies: - "@typescript-eslint/tsconfig-utils": "npm:^8.35.0" - "@typescript-eslint/types": "npm:^8.35.0" + "@typescript-eslint/tsconfig-utils": "npm:^8.48.1" + "@typescript-eslint/types": "npm:^8.48.1" debug: "npm:^4.3.4" peerDependencies: - typescript: ">=4.8.4 <5.9.0" - checksum: 10/a9419da92231aa27f75078fcffab1d02398b50fdb7d5399775a414ba02570682b4b60cdfafb544a021b0dc2372f029c4195f5ae17c50deb11c25661b2ac18a74 + typescript: ">=4.8.4 <6.0.0" + checksum: 10/66ecc7ef9572748860517cde7fbfc335d05ca8c99dcf13ac6d728ac93388d90cdc3ebe2ff33a85c0a03487b3c1c4e36c6e3fe413ee16d8fb003621cb58e65e52 languageName: node linkType: hard @@ -22107,37 +22255,38 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.29.1": - version: 8.29.1 - resolution: "@typescript-eslint/scope-manager@npm:8.29.1" +"@typescript-eslint/scope-manager@npm:8.48.1": + version: 8.48.1 + resolution: "@typescript-eslint/scope-manager@npm:8.48.1" dependencies: - "@typescript-eslint/types": "npm:8.29.1" - "@typescript-eslint/visitor-keys": "npm:8.29.1" - checksum: 10/33a02f490b53436729f5ca2e6e0c5b8db72adb455274e5de43bdaada21033e7941aed1d92653321991e186af77f7794dc0ac35d2fce891cdf65a6d3fb192249e + "@typescript-eslint/types": "npm:8.48.1" + "@typescript-eslint/visitor-keys": "npm:8.48.1" + checksum: 10/5040246220f9872ec47633297b7896ed5587af3163e06ddcb7ca0dcf1e171f359bd4f1c82f794a6adfecbccfb5ef437d51b522321034603c93ba1993c407bdf2 languageName: node linkType: hard -"@typescript-eslint/tsconfig-utils@npm:8.35.0, @typescript-eslint/tsconfig-utils@npm:^8.35.0": - version: 8.35.0 - resolution: "@typescript-eslint/tsconfig-utils@npm:8.35.0" +"@typescript-eslint/tsconfig-utils@npm:8.48.1, @typescript-eslint/tsconfig-utils@npm:^8.48.1": + version: 8.48.1 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.48.1" peerDependencies: - typescript: ">=4.8.4 <5.9.0" - checksum: 10/4160928313ccbe8b169a009b9c1220826c7df7aab427f960c31f3b838931bc7a121ebee8040118481e4528e2e3cf1b26da047c6ac1d802ecff2ef7206026ea6b + typescript: ">=4.8.4 <6.0.0" + checksum: 10/830bcd0e7628441f91899e8e24aaed66d32a239babcc205aba1d08c08ff5a636d8c04f96d9873578df59d7468fc4c5df032667764b3b2ee0a733af36fca21c4a languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:8.29.1": - version: 8.29.1 - resolution: "@typescript-eslint/type-utils@npm:8.29.1" +"@typescript-eslint/type-utils@npm:8.48.1": + version: 8.48.1 + resolution: "@typescript-eslint/type-utils@npm:8.48.1" dependencies: - "@typescript-eslint/typescript-estree": "npm:8.29.1" - "@typescript-eslint/utils": "npm:8.29.1" + "@typescript-eslint/types": "npm:8.48.1" + "@typescript-eslint/typescript-estree": "npm:8.48.1" + "@typescript-eslint/utils": "npm:8.48.1" debug: "npm:^4.3.4" - ts-api-utils: "npm:^2.0.1" + ts-api-utils: "npm:^2.1.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10/3774d6fb32c058b3fa607480e5603fdd2919e07d8babbc00aa703f31c6fd6f7fbc25c25ff246e7e6ac6b77ad0e0b66838a7136aebc31503a58fbe509f68ab2f4 + typescript: ">=4.8.4 <6.0.0" + checksum: 10/6cf9370ac5437e2d64c71964646aed9e6c1ea3c7bb473258b50ae422106461d290f4215b9435b892a2dd563e3c31feb3169532375513b56b7e48f4a425283091 languageName: node linkType: hard @@ -22155,17 +22304,10 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/types@npm:8.29.1": - version: 8.29.1 - resolution: "@typescript-eslint/types@npm:8.29.1" - checksum: 10/99ff59e7af3728858af9b7fdc0165955bcf5cd2eefeaafabfbdd7951fbc8ad869cbb7bed7bcbed6c3c50a8661333b33364dc1de0a0c8f3c64d5882339a5d30dd - languageName: node - linkType: hard - -"@typescript-eslint/types@npm:8.35.0, @typescript-eslint/types@npm:^8.35.0": - version: 8.35.0 - resolution: "@typescript-eslint/types@npm:8.35.0" - checksum: 10/34b5e6da2c59ea84cd528608fff0cc14b102fd23f5517dfee4ef38c9372861d80b5bf92445c9679674f0a4f8dc4ded5066c1bca2bc5569c47515f94568984f35 +"@typescript-eslint/types@npm:8.48.1, @typescript-eslint/types@npm:^8.48.1": + version: 8.48.1 + resolution: "@typescript-eslint/types@npm:8.48.1" + checksum: 10/1aa1e3f25b429bcebd9eb45b5252d950f1b24dbc6014a47dff8d00547e2e1ac47f351846fb996b6ebd49da37a85394051d36191cbbbf2c431b8db9d95afd198d languageName: node linkType: hard @@ -22206,56 +22348,37 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:8.29.1": - version: 8.29.1 - resolution: "@typescript-eslint/typescript-estree@npm:8.29.1" +"@typescript-eslint/typescript-estree@npm:8.48.1, @typescript-eslint/typescript-estree@npm:^8.23.0": + version: 8.48.1 + resolution: "@typescript-eslint/typescript-estree@npm:8.48.1" dependencies: - "@typescript-eslint/types": "npm:8.29.1" - "@typescript-eslint/visitor-keys": "npm:8.29.1" + "@typescript-eslint/project-service": "npm:8.48.1" + "@typescript-eslint/tsconfig-utils": "npm:8.48.1" + "@typescript-eslint/types": "npm:8.48.1" + "@typescript-eslint/visitor-keys": "npm:8.48.1" debug: "npm:^4.3.4" - fast-glob: "npm:^3.3.2" - is-glob: "npm:^4.0.3" - minimatch: "npm:^9.0.4" - semver: "npm:^7.6.0" - ts-api-utils: "npm:^2.0.1" - peerDependencies: - typescript: ">=4.8.4 <5.9.0" - checksum: 10/dded2ebe4c3287443000e3b825e673d0eddb7c48eb2d373c5b7059ea7dbbeba488d7f1de2e42ed7a9299ccff926a65821f2b5594022b49564026ba01c0cc07ab - languageName: node - linkType: hard - -"@typescript-eslint/typescript-estree@npm:^8.23.0": - version: 8.35.0 - resolution: "@typescript-eslint/typescript-estree@npm:8.35.0" - dependencies: - "@typescript-eslint/project-service": "npm:8.35.0" - "@typescript-eslint/tsconfig-utils": "npm:8.35.0" - "@typescript-eslint/types": "npm:8.35.0" - "@typescript-eslint/visitor-keys": "npm:8.35.0" - debug: "npm:^4.3.4" - fast-glob: "npm:^3.3.2" - is-glob: "npm:^4.0.3" minimatch: "npm:^9.0.4" semver: "npm:^7.6.0" + tinyglobby: "npm:^0.2.15" ts-api-utils: "npm:^2.1.0" peerDependencies: - typescript: ">=4.8.4 <5.9.0" - checksum: 10/4dff7c5a8853c8f4e30d35565c62d3ad5bf8445309bd465d94e9bca725853012bb9f58896a04207c30e10b6669511caac8c0f080ed781c93a3db81d5808195aa + typescript: ">=4.8.4 <6.0.0" + checksum: 10/485aa44d22453396dbe61c560c6f583bf876f971d9e70773093cd729279f88184cf5793bf706033bbd8465cce6f9d045b63574727d58d5996519c29e1adbbfe5 languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.29.1, @typescript-eslint/utils@npm:^6.0.0 || ^7.0.0 || ^8.0.0, @typescript-eslint/utils@npm:^8.8.1": - version: 8.29.1 - resolution: "@typescript-eslint/utils@npm:8.29.1" +"@typescript-eslint/utils@npm:8.48.1, @typescript-eslint/utils@npm:^6.0.0 || ^7.0.0 || ^8.0.0, @typescript-eslint/utils@npm:^8.8.1": + version: 8.48.1 + resolution: "@typescript-eslint/utils@npm:8.48.1" dependencies: - "@eslint-community/eslint-utils": "npm:^4.4.0" - "@typescript-eslint/scope-manager": "npm:8.29.1" - "@typescript-eslint/types": "npm:8.29.1" - "@typescript-eslint/typescript-estree": "npm:8.29.1" + "@eslint-community/eslint-utils": "npm:^4.7.0" + "@typescript-eslint/scope-manager": "npm:8.48.1" + "@typescript-eslint/types": "npm:8.48.1" + "@typescript-eslint/typescript-estree": "npm:8.48.1" peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10/1d2c85c97a39e063fe490c0cdb6513716e4735bda0bc937475b44d9224074a5070f888dfed1e75f4a91c8f4d825b44fce90b685e07bda391dfeb2553b9b39a5a + typescript: ">=4.8.4 <6.0.0" + checksum: 10/34afe5cf78020b682473e6529d6268eb8015bdb020a3c5303c4abb230d4d7c39e6fc8b9df58d1f0f35a1ceeb5d6182e71e42fe7a28dde8ffc31f8560f2dacc7c languageName: node linkType: hard @@ -22311,23 +22434,13 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.29.1": - version: 8.29.1 - resolution: "@typescript-eslint/visitor-keys@npm:8.29.1" +"@typescript-eslint/visitor-keys@npm:8.48.1": + version: 8.48.1 + resolution: "@typescript-eslint/visitor-keys@npm:8.48.1" dependencies: - "@typescript-eslint/types": "npm:8.29.1" - eslint-visitor-keys: "npm:^4.2.0" - checksum: 10/788290c369c13403692d857e0a464b5c2223e1fef28c717b7dbc61140d33697ce43c7d1a7cb7ac585f2012fc702ce2ec489363d34bf566303d3c48fa494803c5 - languageName: node - linkType: hard - -"@typescript-eslint/visitor-keys@npm:8.35.0": - version: 8.35.0 - resolution: "@typescript-eslint/visitor-keys@npm:8.35.0" - dependencies: - "@typescript-eslint/types": "npm:8.35.0" + "@typescript-eslint/types": "npm:8.48.1" eslint-visitor-keys: "npm:^4.2.1" - checksum: 10/c0acb13aac3a2be5e82844f7d2e86137347efdd04661dbf9fa69ef04a19dd2f1eb2f1eb6bfbfbaada78a46884308d2c0e0b5d0d1a094c84f2dfb670b67ac2b3b + checksum: 10/63aa165c57e6b38700adf84da2e90537577cdeb69d05031e3e70785fa412d96d539dc4c1696a0b7bc93284613f8b92fb1bb40f6068bb75347a942120b246ac60 languageName: node linkType: hard @@ -22376,10 +22489,145 @@ __metadata: languageName: node linkType: hard -"@ungap/structured-clone@npm:^1.2.0": - version: 1.2.0 - resolution: "@ungap/structured-clone@npm:1.2.0" - checksum: 10/c6fe89a505e513a7592e1438280db1c075764793a2397877ff1351721fe8792a966a5359769e30242b3cd023f2efb9e63ca2ca88019d73b564488cc20e3eab12 +"@ungap/structured-clone@npm:^1.2.0, @ungap/structured-clone@npm:^1.3.0": + version: 1.3.0 + resolution: "@ungap/structured-clone@npm:1.3.0" + checksum: 10/80d6910946f2b1552a2406650051c91bbd1f24a6bf854354203d84fe2714b3e8ce4618f49cc3410494173a1c1e8e9777372fe68dce74bd45faf0a7a1a6ccf448 + languageName: node + linkType: hard + +"@unrs/resolver-binding-android-arm-eabi@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-android-arm-eabi@npm:1.11.1" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@unrs/resolver-binding-android-arm64@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-android-arm64@npm:1.11.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@unrs/resolver-binding-darwin-arm64@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-darwin-arm64@npm:1.11.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@unrs/resolver-binding-darwin-x64@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-darwin-x64@npm:1.11.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@unrs/resolver-binding-freebsd-x64@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-freebsd-x64@npm:1.11.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-arm-gnueabihf@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-linux-arm-gnueabihf@npm:1.11.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-arm-musleabihf@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-linux-arm-musleabihf@npm:1.11.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-arm64-gnu@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-linux-arm64-gnu@npm:1.11.1" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-arm64-musl@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-linux-arm64-musl@npm:1.11.1" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-ppc64-gnu@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-linux-ppc64-gnu@npm:1.11.1" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-riscv64-gnu@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-linux-riscv64-gnu@npm:1.11.1" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-riscv64-musl@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-linux-riscv64-musl@npm:1.11.1" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-s390x-gnu@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-linux-s390x-gnu@npm:1.11.1" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-x64-gnu@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-linux-x64-gnu@npm:1.11.1" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-x64-musl@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-linux-x64-musl@npm:1.11.1" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@unrs/resolver-binding-wasm32-wasi@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-wasm32-wasi@npm:1.11.1" + dependencies: + "@napi-rs/wasm-runtime": "npm:^0.2.11" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@unrs/resolver-binding-win32-arm64-msvc@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-win32-arm64-msvc@npm:1.11.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@unrs/resolver-binding-win32-ia32-msvc@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-win32-ia32-msvc@npm:1.11.1" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@unrs/resolver-binding-win32-x64-msvc@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-win32-x64-msvc@npm:1.11.1" + conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -22714,7 +22962,7 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/ast@npm:1.14.1, @webassemblyjs/ast@npm:^1.12.1, @webassemblyjs/ast@npm:^1.14.1": +"@webassemblyjs/ast@npm:1.14.1, @webassemblyjs/ast@npm:^1.14.1": version: 1.14.1 resolution: "@webassemblyjs/ast@npm:1.14.1" dependencies: @@ -22800,7 +23048,7 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/wasm-edit@npm:^1.12.1, @webassemblyjs/wasm-edit@npm:^1.14.1": +"@webassemblyjs/wasm-edit@npm:^1.14.1": version: 1.14.1 resolution: "@webassemblyjs/wasm-edit@npm:1.14.1" dependencies: @@ -22841,7 +23089,7 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/wasm-parser@npm:1.14.1, @webassemblyjs/wasm-parser@npm:^1.12.1, @webassemblyjs/wasm-parser@npm:^1.14.1": +"@webassemblyjs/wasm-parser@npm:1.14.1, @webassemblyjs/wasm-parser@npm:^1.14.1": version: 1.14.1 resolution: "@webassemblyjs/wasm-parser@npm:1.14.1" dependencies: @@ -23702,13 +23950,6 @@ __metadata: languageName: node linkType: hard -"abab@npm:^2.0.6": - version: 2.0.6 - resolution: "abab@npm:2.0.6" - checksum: 10/ebe95d7278999e605823fc515a3b05d689bc72e7f825536e73c95ebf621636874c6de1b749b3c4bf866b96ccd4b3a2802efa313d0e45ad51a413c8c73247db20 - languageName: node - linkType: hard - "abbrev@npm:1": version: 1.1.1 resolution: "abbrev@npm:1.1.1" @@ -23752,16 +23993,6 @@ __metadata: languageName: node linkType: hard -"acorn-globals@npm:^6.0.0": - version: 6.0.0 - resolution: "acorn-globals@npm:6.0.0" - dependencies: - acorn: "npm:^7.1.1" - acorn-walk: "npm:^7.1.1" - checksum: 10/72d95e5b5e585f9acd019b993ab8bbba68bb3cbc9d9b5c1ebb3c2f1fe5981f11deababfb4949f48e6262f9c57878837f5958c0cca396f81023814680ca878042 - languageName: node - linkType: hard - "acorn-import-attributes@npm:^1.9.5": version: 1.9.5 resolution: "acorn-import-attributes@npm:1.9.5" @@ -23789,13 +24020,6 @@ __metadata: languageName: node linkType: hard -"acorn-walk@npm:^7.1.1": - version: 7.1.1 - resolution: "acorn-walk@npm:7.1.1" - checksum: 10/f3cbf2f7eba3bd27b6e2c0a68d10304de365581fa30b129d0420697135791f44a6d1b0da341d40a2e8af319574f4bd5cb970279dd35fdd1fc5beacaba29c6a4e - languageName: node - linkType: hard - "acorn-walk@npm:^8.1.1": version: 8.2.0 resolution: "acorn-walk@npm:8.2.0" @@ -23803,16 +24027,7 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^7.1.1": - version: 7.4.1 - resolution: "acorn@npm:7.4.1" - bin: - acorn: bin/acorn - checksum: 10/8be2a40714756d713dfb62544128adce3b7102c6eb94bc312af196c2cc4af76e5b93079bd66b05e9ca31b35a9b0ce12171d16bc55f366cafdb794fdab9d753ec - languageName: node - linkType: hard - -"acorn@npm:^8.14.0, acorn@npm:^8.15.0, acorn@npm:^8.4.1, acorn@npm:^8.7.1, acorn@npm:^8.8.2, acorn@npm:^8.9.0": +"acorn@npm:^8.14.0, acorn@npm:^8.15.0, acorn@npm:^8.4.1, acorn@npm:^8.8.2, acorn@npm:^8.9.0": version: 8.15.0 resolution: "acorn@npm:8.15.0" bin: @@ -23976,7 +24191,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^6.12.2, ajv@npm:^6.12.4, ajv@npm:^6.12.5, ajv@npm:^6.12.6": +"ajv@npm:^6.12.2, ajv@npm:^6.12.4, ajv@npm:^6.12.5": version: 6.12.6 resolution: "ajv@npm:6.12.6" dependencies: @@ -24042,6 +24257,13 @@ __metadata: languageName: node linkType: hard +"anser@npm:^2.1.1": + version: 2.3.3 + resolution: "anser@npm:2.3.3" + checksum: 10/fd42896c28defc2489f384930e277c6604651839469bef833a57dfd3ad4d7a02c0ddfc0e464c23f907c37724994e5f4aae5a0aa5fad50230e1c45582f3b87aa4 + languageName: node + linkType: hard + "ansi-align@npm:^3.0.0": version: 3.0.1 resolution: "ansi-align@npm:3.0.1" @@ -24085,15 +24307,6 @@ __metadata: languageName: node linkType: hard -"ansi-html@npm:^0.0.9": - version: 0.0.9 - resolution: "ansi-html@npm:0.0.9" - bin: - ansi-html: bin/ansi-html - checksum: 10/3e83fae364d323d9c453f74a21aa29da68ae152e996c66de45a49a445ea362c4e2e9abce0069558239ff23e3d6ae73b5d27993d631382aa83d85f44b687e0aa1 - languageName: node - linkType: hard - "ansi-regex@npm:*, ansi-regex@npm:^6.0.1": version: 6.2.2 resolution: "ansi-regex@npm:6.2.2" @@ -24133,7 +24346,7 @@ __metadata: languageName: node linkType: hard -"ansi-styles@npm:^5.0.0": +"ansi-styles@npm:^5.0.0, ansi-styles@npm:^5.2.0": version: 5.2.0 resolution: "ansi-styles@npm:5.2.0" checksum: 10/d7f4e97ce0623aea6bc0d90dcd28881ee04cba06c570b97fd3391bd7a268eedfd9d5e2dd4fdcbdd82b8105df5faf6f24aaedc08eaf3da898e702db5948f63469 @@ -24154,13 +24367,13 @@ __metadata: languageName: node linkType: hard -"anymatch@npm:^3.0.3, anymatch@npm:~3.1.2": - version: 3.1.2 - resolution: "anymatch@npm:3.1.2" +"anymatch@npm:^3.1.3, anymatch@npm:~3.1.2": + version: 3.1.3 + resolution: "anymatch@npm:3.1.3" dependencies: normalize-path: "npm:^3.0.0" picomatch: "npm:^2.0.4" - checksum: 10/985163db2292fac9e5a1e072bf99f1b5baccf196e4de25a0b0b81865ebddeb3b3eb4480734ef0a2ac8c002845396b91aa89121f5b84f93981a4658164a9ec6e9 + checksum: 10/3e044fd6d1d26545f235a9fe4d7a534e2029d8e59fa7fd9f2a6eb21230f6b5380ea1eaf55136e60cbf8e613544b3b766e7a6fa2102e2a3a117505466e3025dc2 languageName: node linkType: hard @@ -24358,17 +24571,19 @@ __metadata: languageName: node linkType: hard -"array-includes@npm:^3.1.6, array-includes@npm:^3.1.8": - version: 3.1.8 - resolution: "array-includes@npm:3.1.8" +"array-includes@npm:^3.1.6, array-includes@npm:^3.1.8, array-includes@npm:^3.1.9": + version: 3.1.9 + resolution: "array-includes@npm:3.1.9" dependencies: - call-bind: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.2" - es-object-atoms: "npm:^1.0.0" - get-intrinsic: "npm:^1.2.4" - is-string: "npm:^1.0.7" - checksum: 10/290b206c9451f181fb2b1f79a3bf1c0b66bb259791290ffbada760c79b284eef6f5ae2aeb4bcff450ebc9690edd25732c4c73a3c2b340fcc0f4563aed83bf488 + es-abstract: "npm:^1.24.0" + es-object-atoms: "npm:^1.1.1" + get-intrinsic: "npm:^1.3.0" + is-string: "npm:^1.1.1" + math-intrinsics: "npm:^1.1.0" + checksum: 10/8bfe9a58df74f326b4a76b04ee05c13d871759e888b4ee8f013145297cf5eb3c02cfa216067ebdaac5d74eb9763ac5cad77cdf2773b8ab475833701e032173aa languageName: node linkType: hard @@ -24400,29 +24615,30 @@ __metadata: languageName: node linkType: hard -"array.prototype.findlastindex@npm:^1.2.5": - version: 1.2.5 - resolution: "array.prototype.findlastindex@npm:1.2.5" +"array.prototype.findlastindex@npm:^1.2.6": + version: 1.2.6 + resolution: "array.prototype.findlastindex@npm:1.2.6" dependencies: - call-bind: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.2" + es-abstract: "npm:^1.23.9" es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.0.0" - es-shim-unscopables: "npm:^1.0.2" - checksum: 10/7c5c821f357cd53ab6cc305de8086430dd8d7a2485db87b13f843e868055e9582b1fd338f02338f67fc3a1603ceaf9610dd2a470b0b506f9d18934780f95b246 + es-object-atoms: "npm:^1.1.1" + es-shim-unscopables: "npm:^1.1.0" + checksum: 10/5ddb6420e820bef6ddfdcc08ce780d0fd5e627e97457919c27e32359916de5a11ce12f7c55073555e503856618eaaa70845d6ca11dcba724766f38eb1c22f7a2 languageName: node linkType: hard -"array.prototype.flat@npm:^1.3.1, array.prototype.flat@npm:^1.3.2": - version: 1.3.2 - resolution: "array.prototype.flat@npm:1.3.2" +"array.prototype.flat@npm:^1.3.1, array.prototype.flat@npm:^1.3.3": + version: 1.3.3 + resolution: "array.prototype.flat@npm:1.3.3" dependencies: - call-bind: "npm:^1.0.2" - define-properties: "npm:^1.2.0" - es-abstract: "npm:^1.22.1" - es-shim-unscopables: "npm:^1.0.0" - checksum: 10/d9d2f6f27584de92ec7995bc931103e6de722cd2498bdbfc4cba814fc3e52f056050a93be883018811f7c0a35875f5056584a0e940603a5e5934f0279896aebe + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.5" + es-shim-unscopables: "npm:^1.0.2" + checksum: 10/f9b992fa0775d8f7c97abc91eb7f7b2f0ed8430dd9aeb9fdc2967ac4760cdd7fc2ef7ead6528fef40c7261e4d790e117808ce0d3e7e89e91514d4963a531cd01 languageName: node linkType: hard @@ -24789,7 +25005,7 @@ __metadata: languageName: node linkType: hard -"axios@npm:1.13.2, axios@npm:^1.0.0, axios@npm:^1.11.0, axios@npm:^1.13.0, axios@npm:^1.7.4, axios@npm:^1.9.0": +"axios@npm:1.13.2, axios@npm:^1.0.0, axios@npm:^1.11.0, axios@npm:^1.12.2, axios@npm:^1.13.0, axios@npm:^1.7.4": version: 1.13.2 resolution: "axios@npm:1.13.2" dependencies: @@ -24824,45 +25040,42 @@ __metadata: languageName: node linkType: hard -"babel-jest@npm:^29.7.0": - version: 29.7.0 - resolution: "babel-jest@npm:29.7.0" +"babel-jest@npm:30.2.0": + version: 30.2.0 + resolution: "babel-jest@npm:30.2.0" dependencies: - "@jest/transform": "npm:^29.7.0" - "@types/babel__core": "npm:^7.1.14" - babel-plugin-istanbul: "npm:^6.1.1" - babel-preset-jest: "npm:^29.6.3" - chalk: "npm:^4.0.0" - graceful-fs: "npm:^4.2.9" + "@jest/transform": "npm:30.2.0" + "@types/babel__core": "npm:^7.20.5" + babel-plugin-istanbul: "npm:^7.0.1" + babel-preset-jest: "npm:30.2.0" + chalk: "npm:^4.1.2" + graceful-fs: "npm:^4.2.11" slash: "npm:^3.0.0" peerDependencies: - "@babel/core": ^7.8.0 - checksum: 10/8a0953bd813b3a8926008f7351611055548869e9a53dd36d6e7e96679001f71e65fd7dbfe253265c3ba6a4e630dc7c845cf3e78b17d758ef1880313ce8fba258 + "@babel/core": ^7.11.0 || ^8.0.0-0 + checksum: 10/4c7351a366cf8ac2b8a2e4e438867693eb9d83ed24c29c648da4576f700767aaf72a5d14337fc3f92c50b069f5025b26c7b89e3b7b867914b7cf8997fc15f095 languageName: node linkType: hard -"babel-plugin-istanbul@npm:^6.1.1": - version: 6.1.1 - resolution: "babel-plugin-istanbul@npm:6.1.1" +"babel-plugin-istanbul@npm:^7.0.1": + version: 7.0.1 + resolution: "babel-plugin-istanbul@npm:7.0.1" dependencies: "@babel/helper-plugin-utils": "npm:^7.0.0" "@istanbuljs/load-nyc-config": "npm:^1.0.0" - "@istanbuljs/schema": "npm:^0.1.2" - istanbul-lib-instrument: "npm:^5.0.4" + "@istanbuljs/schema": "npm:^0.1.3" + istanbul-lib-instrument: "npm:^6.0.2" test-exclude: "npm:^6.0.0" - checksum: 10/ffd436bb2a77bbe1942a33245d770506ab2262d9c1b3c1f1da7f0592f78ee7445a95bc2efafe619dd9c1b6ee52c10033d6c7d29ddefe6f5383568e60f31dfe8d + checksum: 10/fe9f865f975aaa7a033de9ccb2b63fdcca7817266c5e98d3e02ac7ffd774c695093d215302796cb3770a71ef4574e7a9b298504c3c0c104cf4b48c8eda67b2a6 languageName: node linkType: hard -"babel-plugin-jest-hoist@npm:^29.6.3": - version: 29.6.3 - resolution: "babel-plugin-jest-hoist@npm:29.6.3" +"babel-plugin-jest-hoist@npm:30.2.0": + version: 30.2.0 + resolution: "babel-plugin-jest-hoist@npm:30.2.0" dependencies: - "@babel/template": "npm:^7.3.3" - "@babel/types": "npm:^7.3.3" - "@types/babel__core": "npm:^7.1.14" - "@types/babel__traverse": "npm:^7.0.6" - checksum: 10/9bfa86ec4170bd805ab8ca5001ae50d8afcb30554d236ba4a7ffc156c1a92452e220e4acbd98daefc12bf0216fccd092d0a2efed49e7e384ec59e0597a926d65 + "@types/babel__core": "npm:^7.20.5" + checksum: 10/360e87a9aa35f4cf208a10ba79e1821ea906f9e3399db2a9762cbc5076fd59f808e571d88b5b1106738d22e23f9ddefbb8137b2780b2abd401c8573b85c8a2f5 languageName: node linkType: hard @@ -24910,37 +25123,40 @@ __metadata: languageName: node linkType: hard -"babel-preset-current-node-syntax@npm:^1.0.0": - version: 1.0.0 - resolution: "babel-preset-current-node-syntax@npm:1.0.0" +"babel-preset-current-node-syntax@npm:^1.2.0": + version: 1.2.0 + resolution: "babel-preset-current-node-syntax@npm:1.2.0" dependencies: "@babel/plugin-syntax-async-generators": "npm:^7.8.4" "@babel/plugin-syntax-bigint": "npm:^7.8.3" - "@babel/plugin-syntax-class-properties": "npm:^7.8.3" - "@babel/plugin-syntax-import-meta": "npm:^7.8.3" + "@babel/plugin-syntax-class-properties": "npm:^7.12.13" + "@babel/plugin-syntax-class-static-block": "npm:^7.14.5" + "@babel/plugin-syntax-import-attributes": "npm:^7.24.7" + "@babel/plugin-syntax-import-meta": "npm:^7.10.4" "@babel/plugin-syntax-json-strings": "npm:^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.10.4" "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" - "@babel/plugin-syntax-numeric-separator": "npm:^7.8.3" + "@babel/plugin-syntax-numeric-separator": "npm:^7.10.4" "@babel/plugin-syntax-object-rest-spread": "npm:^7.8.3" "@babel/plugin-syntax-optional-catch-binding": "npm:^7.8.3" "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" - "@babel/plugin-syntax-top-level-await": "npm:^7.8.3" + "@babel/plugin-syntax-private-property-in-object": "npm:^7.14.5" + "@babel/plugin-syntax-top-level-await": "npm:^7.14.5" peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10/2d45944dc78da3a7ec3bce2a48664bd33e21f5b718a724cc2860921d2e2152678a3de50df9a8d5add8c3c3a10b444925f03ead28090c9dde89ea695044a010db + "@babel/core": ^7.0.0 || ^8.0.0-0 + checksum: 10/3608fa671cfa46364ea6ec704b8fcdd7514b7b70e6ec09b1199e13ae73ed346c51d5ce2cb6d4d5b295f6a3f2cad1fdeec2308aa9e037002dd7c929194cc838ea languageName: node linkType: hard -"babel-preset-jest@npm:^29.6.3": - version: 29.6.3 - resolution: "babel-preset-jest@npm:29.6.3" +"babel-preset-jest@npm:30.2.0": + version: 30.2.0 + resolution: "babel-preset-jest@npm:30.2.0" dependencies: - babel-plugin-jest-hoist: "npm:^29.6.3" - babel-preset-current-node-syntax: "npm:^1.0.0" + babel-plugin-jest-hoist: "npm:30.2.0" + babel-preset-current-node-syntax: "npm:^1.2.0" peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10/aa4ff2a8a728d9d698ed521e3461a109a1e66202b13d3494e41eea30729a5e7cc03b3a2d56c594423a135429c37bf63a9fa8b0b9ce275298be3095a88c69f6fb + "@babel/core": ^7.11.0 || ^8.0.0-beta.1 + checksum: 10/f75e155a8cf63ea1c5ca942bf757b934427630a1eeafdf861e9117879b3367931fc521da3c41fd52f8d59d705d1093ffb46c9474b3fd4d765d194bea5659d7d9 languageName: node linkType: hard @@ -25082,6 +25298,15 @@ __metadata: languageName: node linkType: hard +"baseline-browser-mapping@npm:^2.8.25": + version: 2.9.8 + resolution: "baseline-browser-mapping@npm:2.9.8" + bin: + baseline-browser-mapping: dist/cli.js + checksum: 10/1c9cf2fb0cc008ba86d5f1bd1f1a771d6f508dbd6124d173549c650734d6d305eba49622e8bc0160beea9ecafcd2f5f104ffdc92f4a815a4926b74a2d7a6d986 + languageName: node + linkType: hard + "basic-ftp@npm:^5.0.2": version: 5.0.3 resolution: "basic-ftp@npm:5.0.3" @@ -25112,15 +25337,6 @@ __metadata: languageName: node linkType: hard -"better-opn@npm:^3.0.2": - version: 3.0.2 - resolution: "better-opn@npm:3.0.2" - dependencies: - open: "npm:^8.0.4" - checksum: 10/24668e5a837d0d2c0edf17ad5ebcfeb00a8a5578a5eb09f7a409e1a60617cdfea40b8ebfc95e5f12d9568157930d033e6805788fcf0780413ac982c95d3745d1 - languageName: node - linkType: hard - "better-path-resolve@npm:1.0.0": version: 1.0.0 resolution: "better-path-resolve@npm:1.0.0" @@ -25131,13 +25347,13 @@ __metadata: linkType: hard "better-sqlite3@npm:^12.0.0": - version: 12.4.1 - resolution: "better-sqlite3@npm:12.4.1" + version: 12.5.0 + resolution: "better-sqlite3@npm:12.5.0" dependencies: bindings: "npm:^1.5.0" node-gyp: "npm:latest" prebuild-install: "npm:^7.1.1" - checksum: 10/e8133786677e88a2526c965658178e2e057e4b40ff554895a71ecb5e617e062fea619f31389ffbd6fc1f3396ef598812302be56c32e0f01fcb82610785d18186 + checksum: 10/8d81cde54231430d5b7fca8d7224ccdd13708ea769765e7a284af1744376568553016ed500f55e7ff992c57f12e29044da17a950adda2a4d361a5295c237d2ea languageName: node linkType: hard @@ -25154,6 +25370,15 @@ __metadata: languageName: node linkType: hard +"bidi-js@npm:^1.0.3": + version: 1.0.3 + resolution: "bidi-js@npm:1.0.3" + dependencies: + require-from-string: "npm:^2.0.2" + checksum: 10/c4341c7a98797efe3d186cd99d6f97e9030a4f959794ca200ef2ec0a678483a916335bba6c2c0608a21d04a221288a31c9fd0faa0cd9b3903b93594b42466a6a + languageName: node + linkType: hard + "big.js@npm:^5.2.2": version: 5.2.2 resolution: "big.js@npm:5.2.2" @@ -25271,23 +25496,23 @@ __metadata: languageName: node linkType: hard -"body-parser@npm:1.20.3, body-parser@npm:^1.15.2": - version: 1.20.3 - resolution: "body-parser@npm:1.20.3" +"body-parser@npm:^1.15.2, 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 @@ -25418,13 +25643,6 @@ __metadata: languageName: node linkType: hard -"browser-process-hrtime@npm:^1.0.0": - version: 1.0.0 - resolution: "browser-process-hrtime@npm:1.0.0" - checksum: 10/e30f868cdb770b1201afb714ad1575dd86366b6e861900884665fb627109b3cc757c40067d3bfee1ff2a29c835257ea30725a8018a9afd02ac1c24b408b1e45f - languageName: node - linkType: hard - "browser-resolve@npm:^2.0.0": version: 2.0.0 resolution: "browser-resolve@npm:2.0.0" @@ -25508,17 +25726,18 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.0.0, browserslist@npm:^4.16.0, browserslist@npm:^4.16.6, browserslist@npm:^4.18.1, browserslist@npm:^4.24.0": - version: 4.24.2 - resolution: "browserslist@npm:4.24.2" +"browserslist@npm:^4.0.0, browserslist@npm:^4.16.0, browserslist@npm:^4.16.6, browserslist@npm:^4.18.1, browserslist@npm:^4.24.0, browserslist@npm:^4.26.3": + version: 4.28.0 + resolution: "browserslist@npm:4.28.0" dependencies: - caniuse-lite: "npm:^1.0.30001669" - electron-to-chromium: "npm:^1.5.41" - node-releases: "npm:^2.0.18" - update-browserslist-db: "npm:^1.1.1" + baseline-browser-mapping: "npm:^2.8.25" + caniuse-lite: "npm:^1.0.30001754" + electron-to-chromium: "npm:^1.5.249" + node-releases: "npm:^2.0.27" + update-browserslist-db: "npm:^1.1.4" bin: browserslist: cli.js - checksum: 10/f8a9d78bbabe466c57ffd5c50a9e5582a5df9aa68f43078ca62a9f6d0d6c70ba72eca72d0a574dbf177cf55cdca85a46f7eb474917a47ae5398c66f8b76f7d1c + checksum: 10/59dc88f8d950e44a064361cb874f486e532a8ba932e0cf549aee8b36dd2b791da2bc11f36c1cf820ebb9c1f3250b100f8c56364dd6e86dbc90495af424100e19 languageName: node linkType: hard @@ -25561,7 +25780,7 @@ __metadata: languageName: node linkType: hard -"buffer-equal-constant-time@npm:1.0.1": +"buffer-equal-constant-time@npm:^1.0.1": version: 1.0.1 resolution: "buffer-equal-constant-time@npm:1.0.1" checksum: 10/80bb945f5d782a56f374b292770901065bad21420e34936ecbe949e57724b4a13874f735850dd1cc61f078773c4fb5493a41391e7bda40d1fa388d6bd80daaab @@ -25674,7 +25893,7 @@ __metadata: languageName: node linkType: hard -"bytes@npm:3.1.2, bytes@npm:^3.1.2": +"bytes@npm:3.1.2, bytes@npm:^3.1.2, bytes@npm:~3.1.2": version: 3.1.2 resolution: "bytes@npm:3.1.2" checksum: 10/a10abf2ba70c784471d6b4f58778c0beeb2b5d405148e66affa91f23a9f13d07603d0a0354667310ae1d6dc141474ffd44e2a074be0f6e2254edb8fc21445388 @@ -25881,7 +26100,7 @@ __metadata: languageName: node linkType: hard -"callsites@npm:^3.0.0": +"callsites@npm:^3.0.0, callsites@npm:^3.1.0": version: 3.1.0 resolution: "callsites@npm:3.1.0" checksum: 10/072d17b6abb459c2ba96598918b55868af677154bec7e73d222ef95a8fdb9bbf7dae96a8421085cdad8cd190d86653b5b6dc55a4484f2e5b2e27d5e0c3fc15b3 @@ -25912,7 +26131,7 @@ __metadata: languageName: node linkType: hard -"camelcase@npm:^6.2.0": +"camelcase@npm:^6.2.0, camelcase@npm:^6.3.0": version: 6.3.0 resolution: "camelcase@npm:6.3.0" checksum: 10/8c96818a9076434998511251dcb2761a94817ea17dbdc37f47ac080bd088fc62c7369429a19e2178b993497132c8cbcf5cc1f44ba963e76782ba469c0474938d @@ -25938,10 +26157,10 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001669": - version: 1.0.30001717 - resolution: "caniuse-lite@npm:1.0.30001717" - checksum: 10/e47dfd8707ea305baa177f3d3d531df614f5a9ac6335363fc8f86f0be4caf79f5734f3f68b601fee4edd9d79f1e5ffc0931466bb894bf955ed6b1dd5a1c34b1d +"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001754": + version: 1.0.30001760 + resolution: "caniuse-lite@npm:1.0.30001760" + checksum: 10/ac5c13d00b946c3ace331d25379ed9d85743b1028aba79ae80402cb128b4b491122585144898fefb836d4d9879c13c717a081ae241a00420cbbc7e1b934ec685 languageName: node linkType: hard @@ -26024,6 +26243,13 @@ __metadata: languageName: node linkType: hard +"character-entities-legacy@npm:^3.0.0": + version: 3.0.0 + resolution: "character-entities-legacy@npm:3.0.0" + checksum: 10/7582af055cb488b626d364b7d7a4e46b06abd526fb63c0e4eb35bcb9c9799cc4f76b39f34fdccef2d1174ac95e53e9ab355aae83227c1a2505877893fce77731 + languageName: node + linkType: hard + "character-entities@npm:^1.0.0": version: 1.2.4 resolution: "character-entities@npm:1.2.4" @@ -26052,7 +26278,7 @@ __metadata: languageName: node linkType: hard -"chardet@npm:^2.1.0": +"chardet@npm:^2.1.1": version: 2.1.1 resolution: "chardet@npm:2.1.1" checksum: 10/d56913b65e45c5c86f331988e2ef6264c131bfeadaae098ee719bf6610546c77740e37221ffec802dde56b5e4466613a4c754786f4da6b5f6c5477243454d324 @@ -26099,6 +26325,15 @@ __metadata: languageName: node linkType: hard +"chokidar@npm:^4.0.1": + version: 4.0.3 + resolution: "chokidar@npm:4.0.3" + dependencies: + readdirp: "npm:^4.0.1" + checksum: 10/bf2a575ea5596000e88f5db95461a9d59ad2047e939d5a4aac59dd472d126be8f1c1ff3c7654b477cf532d18f42a97279ef80ee847972fd2a25410bf00b80b59 + languageName: node + linkType: hard + "chownr@npm:^1.1.1": version: 1.1.4 resolution: "chownr@npm:1.1.4" @@ -26143,10 +26378,10 @@ __metadata: languageName: node linkType: hard -"ci-info@npm:^4.0.0": - version: 4.0.0 - resolution: "ci-info@npm:4.0.0" - checksum: 10/c983bb7ff1b06648f4a47432201abbd58291147d8ab5043dbb5c03e1a0e3fb2347f40d29b66a3044f28ffeb5dade01ac35aa6bd4e7464a44d9a49a3d7532415a +"ci-info@npm:^4.0.0, ci-info@npm:^4.2.0": + version: 4.3.1 + resolution: "ci-info@npm:4.3.1" + checksum: 10/9dc952bef67e665ccde2e7a552d42d5d095529d21829ece060a00925ede2dfa136160c70ef2471ea6ed6c9b133218b47c007f56955c0f1734a2e57f240aa7445 languageName: node linkType: hard @@ -26169,13 +26404,20 @@ __metadata: languageName: node linkType: hard -"cjs-module-lexer@npm:^1.0.0, cjs-module-lexer@npm:^1.2.2": +"cjs-module-lexer@npm:^1.2.2": version: 1.4.3 resolution: "cjs-module-lexer@npm:1.4.3" checksum: 10/d2b92f919a2dedbfd61d016964fce8da0035f827182ed6839c97cac56e8a8077cfa6a59388adfe2bc588a19cef9bbe830d683a76a6e93c51f65852062cfe2591 languageName: node linkType: hard +"cjs-module-lexer@npm:^2.1.0": + version: 2.1.1 + resolution: "cjs-module-lexer@npm:2.1.1" + checksum: 10/db4331152f4414f3a451865554290eb7d146699aa2aefe1504891951d791ab450f1a75b0477630a4f2942be26f257c4e0627fbfed29d322b5266956b3643f3c1 + languageName: node + linkType: hard + "classnames@npm:*, classnames@npm:^2.2.6, classnames@npm:^2.3.1, classnames@npm:^2.5.1": version: 2.5.1 resolution: "classnames@npm:2.5.1" @@ -26527,14 +26769,14 @@ __metadata: languageName: node linkType: hard -"collect-v8-coverage@npm:^1.0.0": - version: 1.0.0 - resolution: "collect-v8-coverage@npm:1.0.0" - checksum: 10/99d6059eb0e04bf792e27824d43d1139ede28914b7f55ddedd9c43594926221579fa6925ecc7e2f47dec14f5199e5ea3afbfc1da9a32ad542f859022d57f9d80 +"collect-v8-coverage@npm:^1.0.2": + version: 1.0.3 + resolution: "collect-v8-coverage@npm:1.0.3" + checksum: 10/656443261fb7b79cf79e89cba4b55622b07c1d4976c630829d7c5c585c73cda1c2ff101f316bfb19bb9e2c58d724c7db1f70a21e213dcd14099227c5e6019860 languageName: node linkType: hard -"color-convert@npm:^1.9.0, color-convert@npm:^1.9.1": +"color-convert@npm:^1.9.0": version: 1.9.3 resolution: "color-convert@npm:1.9.3" dependencies: @@ -26552,6 +26794,15 @@ __metadata: languageName: node linkType: hard +"color-convert@npm:^3.1.3": + version: 3.1.3 + resolution: "color-convert@npm:3.1.3" + dependencies: + color-name: "npm:^2.0.0" + checksum: 10/36b9b99c138f90eb11a28d1ad911054a9facd6cffde4f00dc49a34ebde7cae28454b2285ede64f273b6a8df9c3228b80e4352f4471978fa8b5005fe91341a67b + languageName: node + linkType: hard + "color-name@npm:1.1.3": version: 1.1.3 resolution: "color-name@npm:1.1.3" @@ -26559,20 +26810,26 @@ __metadata: languageName: node linkType: hard -"color-name@npm:^1.0.0, color-name@npm:^1.1.4, color-name@npm:~1.1.4": +"color-name@npm:^1.1.4, color-name@npm:~1.1.4": version: 1.1.4 resolution: "color-name@npm:1.1.4" checksum: 10/b0445859521eb4021cd0fb0cc1a75cecf67fceecae89b63f62b201cca8d345baf8b952c966862a9d9a2632987d4f6581f0ec8d957dfacece86f0a7919316f610 languageName: node linkType: hard -"color-string@npm:^1.5.2": - version: 1.9.0 - resolution: "color-string@npm:1.9.0" +"color-name@npm:^2.0.0": + version: 2.1.0 + resolution: "color-name@npm:2.1.0" + checksum: 10/eb014f71d87408e318e95d3f554f188370d354ba8e0ffa4341d0fd19de391bfe2bc96e563d4f6614644d676bc24f475560dffee3fe310c2d6865d007410a9a2b + languageName: node + linkType: hard + +"color-string@npm:^2.1.3": + version: 2.1.4 + resolution: "color-string@npm:2.1.4" dependencies: - color-name: "npm:^1.0.0" - simple-swizzle: "npm:^0.2.2" - checksum: 10/6e347b463aa8e40eb193d6ee21ef501c88dad9c20c4607f5394f3b3c4ce40d828c87a35ac4acdc94696d8dae00a04cb30f0bc73f001ccc812f1d58dccaf26591 + color-name: "npm:^2.0.0" + checksum: 10/689a8688ac3cd55247792c83a9db9bfe675343c7412fedba1eb748ac6a8867dd2bb3d406e309ebfe90336809ee5067c7f2cccfbd10133c5cc9ef1dba5aad58f2 languageName: node linkType: hard @@ -26585,13 +26842,13 @@ __metadata: languageName: node linkType: hard -"color@npm:3.0.x": - version: 3.0.0 - resolution: "color@npm:3.0.0" +"color@npm:^5.0.2": + version: 5.0.3 + resolution: "color@npm:5.0.3" dependencies: - color-convert: "npm:^1.9.1" - color-string: "npm:^1.5.2" - checksum: 10/17ca34cc7d1aa1b41cb414c388ed0376e70401b42e9ca0320ee095f664373bc76f5934d81cb8e118ab6795994ca8abe396261da6ad308d5151b4dcf03cfc9cf9 + color-convert: "npm:^3.1.3" + color-string: "npm:^2.1.3" + checksum: 10/88063ee058b995e5738092b5aa58888666275d1e967333f3814ff4fa334ce9a9e71de78a16fb1838f17c80793ea87f4878c20192037662809fe14eab2d474fd9 languageName: node linkType: hard @@ -26623,16 +26880,6 @@ __metadata: languageName: node linkType: hard -"colorspace@npm:1.1.x": - version: 1.1.2 - resolution: "colorspace@npm:1.1.2" - dependencies: - color: "npm:3.0.x" - text-hex: "npm:1.0.x" - checksum: 10/a959ec1669176aa72185067b7d04dae1cef2698456e1a452a035ce8adcac95673fbb1547e3240903355bcbaa67e031cca0b8b4f7d42c256b3dd94dcead8e1405 - languageName: node - linkType: hard - "combined-stream@npm:^1.0.8": version: 1.0.8 resolution: "combined-stream@npm:1.0.8" @@ -26719,7 +26966,7 @@ __metadata: languageName: node linkType: hard -"commander@npm:^4.0.0, commander@npm:^4.1.1": +"commander@npm:^4.0.0": version: 4.1.1 resolution: "commander@npm:4.1.1" checksum: 10/3b2dc4125f387dab73b3294dbcb0ab2a862f9c0ad748ee2b27e3544d25325b7a8cdfbcc228d103a98a716960b14478114a5206b5415bd48cdafa38797891562c @@ -26992,7 +27239,7 @@ __metadata: languageName: node linkType: hard -"content-disposition@npm:0.5.4, content-disposition@npm:^0.5.3, content-disposition@npm:~0.5.2": +"content-disposition@npm:^0.5.3, content-disposition@npm:~0.5.2, content-disposition@npm:~0.5.4": version: 0.5.4 resolution: "content-disposition@npm:0.5.4" dependencies: @@ -27055,7 +27302,7 @@ __metadata: languageName: node linkType: hard -"cookie-signature@npm:1.0.7": +"cookie-signature@npm:1.0.7, cookie-signature@npm:~1.0.6": version: 1.0.7 resolution: "cookie-signature@npm:1.0.7" checksum: 10/1a62808cd30d15fb43b70e19829b64d04b0802d8ef00275b57d152de4ae6a3208ca05c197b6668d104c4d9de389e53ccc2d3bc6bcaaffd9602461417d8c40710 @@ -27069,14 +27316,7 @@ __metadata: languageName: node linkType: hard -"cookie@npm:0.7.1": - version: 0.7.1 - resolution: "cookie@npm:0.7.1" - checksum: 10/aec6a6aa0781761bf55d60447d6be08861d381136a0fe94aa084fddd4f0300faa2b064df490c6798adfa1ebaef9e0af9b08a189c823e0811b8b313b3d9a03380 - languageName: node - linkType: hard - -"cookie@npm:0.7.2, cookie@npm:^0.7.0, cookie@npm:^0.7.1, cookie@npm:^0.7.2": +"cookie@npm:0.7.2, cookie@npm:^0.7.0, cookie@npm:^0.7.1, cookie@npm:^0.7.2, cookie@npm:~0.7.1": version: 0.7.2 resolution: "cookie@npm:0.7.2" checksum: 10/24b286c556420d4ba4e9bc09120c9d3db7d28ace2bd0f8ccee82422ce42322f73c8312441271e5eefafbead725980e5996cc02766dbb89a90ac7f5636ede608f @@ -27131,9 +27371,9 @@ __metadata: linkType: hard "core-js@npm:^3.6.5": - version: 3.46.0 - resolution: "core-js@npm:3.46.0" - checksum: 10/82993ca487c6cbbf8bbf00e45eeb9705eb63dc2f9c90d7f35696733efbc3f4b52426e1f8dbef0f0b68ea16caa21e4f44cc5490e08120e1cad4a72b031ed8adaa + version: 3.47.0 + resolution: "core-js@npm:3.47.0" + checksum: 10/c02dc6a091c7e6799e3527dc06a428c44bbcff7f8f6ee700ff818b90aa2ebaf1f17b0234146e692811da97cda5a39a6095ecadec9fd1a74b1135103eb0e96cb1 languageName: node linkType: hard @@ -27295,23 +27535,6 @@ __metadata: languageName: node linkType: hard -"create-jest@npm:^29.7.0": - version: 29.7.0 - resolution: "create-jest@npm:29.7.0" - dependencies: - "@jest/types": "npm:^29.6.3" - chalk: "npm:^4.0.0" - exit: "npm:^0.1.2" - graceful-fs: "npm:^4.2.9" - jest-config: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - prompts: "npm:^2.0.1" - bin: - create-jest: bin/create-jest.js - checksum: 10/847b4764451672b4174be4d5c6d7d63442ec3aa5f3de52af924e4d996d87d7801c18e125504f25232fc75840f6625b3ac85860fac6ce799b5efae7bdcaf4a2b7 - languageName: node - linkType: hard - "create-require@npm:^1.1.0, create-require@npm:^1.1.1": version: 1.1.1 resolution: "create-require@npm:1.1.1" @@ -27571,6 +27794,16 @@ __metadata: languageName: node linkType: hard +"css-tree@npm:^3.1.0": + version: 3.1.0 + resolution: "css-tree@npm:3.1.0" + dependencies: + mdn-data: "npm:2.12.2" + source-map-js: "npm:^1.0.1" + checksum: 10/e8c5c8e98e3aa4a620fda0b813ce57ccf99281652bf9d23e5cdfc9961c9a93a6769941f9a92e31e65d90f446f42fa83879ab0185206dc7a178d9f656d0913e14 + languageName: node + linkType: hard + "css-vendor@npm:^2.0.8": version: 2.0.8 resolution: "css-vendor@npm:2.0.8" @@ -27675,35 +27908,24 @@ __metadata: languageName: node linkType: hard -"cssom@npm:^0.5.0": - version: 0.5.0 - resolution: "cssom@npm:0.5.0" - checksum: 10/b502a315b1ce020a692036cc38cb36afa44157219b80deadfa040ab800aa9321fcfbecf02fd2e6ec87db169715e27978b4ab3701f916461e9cf7808899f23b54 - languageName: node - linkType: hard - -"cssom@npm:~0.3.6": - version: 0.3.8 - resolution: "cssom@npm:0.3.8" - checksum: 10/49eacc88077555e419646c0ea84ddc73c97e3a346ad7cb95e22f9413a9722d8964b91d781ce21d378bd5ae058af9a745402383fa4e35e9cdfd19654b63f892a9 - languageName: node - linkType: hard - -"cssstyle@npm:^2.3.0": - version: 2.3.0 - resolution: "cssstyle@npm:2.3.0" - dependencies: - cssom: "npm:~0.3.6" - checksum: 10/46f7f05a153446c4018b0454ee1464b50f606cb1803c90d203524834b7438eb52f3b173ba0891c618f380ced34ee12020675dc0052a7f1be755fe4ebc27ee977 - languageName: node - linkType: hard - "cssstyle@npm:^4.1.0": - version: 4.1.0 - resolution: "cssstyle@npm:4.1.0" + version: 4.6.0 + resolution: "cssstyle@npm:4.6.0" dependencies: - rrweb-cssom: "npm:^0.7.1" - checksum: 10/8ca9e2d1f1b24f93bb5f3f20a7a1e271e58060957880e985ee55614e196a798ffab309ec6bac105af8a439a6764546761813835ebb7f929d60823637ee838a8f + "@asamuzakjp/css-color": "npm:^3.2.0" + rrweb-cssom: "npm:^0.8.0" + checksum: 10/1cb25c9d66b87adb165f978b75cdeb6f225d7e31ba30a8934666046a0be037e4e7200d359bfa79d4f1a4aef1083ea09633b81bcdb36a2f2ac888e8c73ea3a289 + languageName: node + linkType: hard + +"cssstyle@npm:^5.3.4": + version: 5.3.4 + resolution: "cssstyle@npm:5.3.4" + dependencies: + "@asamuzakjp/css-color": "npm:^4.1.0" + "@csstools/css-syntax-patches-for-csstree": "npm:1.0.14" + css-tree: "npm:^3.1.0" + checksum: 10/4eeb85cbaba47c2e4eb9f9a9dea9633e311f1cb706258f582fc8d099edc0b8c0deea93a6943981960d7f920ac0f3bbb5277cc4c355915406c17a95d0049a073d languageName: node linkType: hard @@ -27864,17 +28086,6 @@ __metadata: languageName: node linkType: hard -"data-urls@npm:^3.0.2": - version: 3.0.2 - resolution: "data-urls@npm:3.0.2" - dependencies: - abab: "npm:^2.0.6" - whatwg-mimetype: "npm:^3.0.0" - whatwg-url: "npm:^11.0.0" - checksum: 10/033fc3dd0fba6d24bc9a024ddcf9923691dd24f90a3d26f6545d6a2f71ec6956f93462f2cdf2183cc46f10dc01ed3bcb36731a8208456eb1a08147e571fe2a76 - languageName: node - linkType: hard - "data-urls@npm:^5.0.0": version: 5.0.0 resolution: "data-urls@npm:5.0.0" @@ -27885,6 +28096,16 @@ __metadata: languageName: node linkType: hard +"data-urls@npm:^6.0.0": + version: 6.0.0 + resolution: "data-urls@npm:6.0.0" + dependencies: + whatwg-mimetype: "npm:^4.0.0" + whatwg-url: "npm:^15.0.0" + checksum: 10/a47f0dde184337c4f168d455aedf0b486fed87b6ca583b4b9ad55d1515f4836b418d4bdc5b5b6fc55e321feb826029586a0d47e1c9a9e7ac4d52a78faceb7fb0 + languageName: node + linkType: hard + "data-view-buffer@npm:^1.0.2": version: 1.0.2 resolution: "data-view-buffer@npm:1.0.2" @@ -27987,7 +28208,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:^4.3.7, debug@npm:^4.4.0, debug@npm:^4.4.1": +"debug@npm:4, debug@npm:4.4.3, debug@npm:^4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:^4.3.7, debug@npm:^4.4.0, debug@npm:^4.4.1, debug@npm:^4.4.3": version: 4.4.3 resolution: "debug@npm:4.4.3" dependencies: @@ -28011,18 +28232,6 @@ __metadata: languageName: node linkType: hard -"debug@npm:4.4.1": - version: 4.4.1 - resolution: "debug@npm:4.4.1" - dependencies: - ms: "npm:^2.1.3" - peerDependenciesMeta: - supports-color: - optional: true - checksum: 10/8e2709b2144f03c7950f8804d01ccb3786373df01e406a0f66928e47001cf2d336cbed9ee137261d4f90d68d8679468c755e3548ed83ddacdc82b194d2468afe - languageName: node - linkType: hard - "debug@npm:^3.1.1, debug@npm:^3.2.7": version: 3.2.7 resolution: "debug@npm:3.2.7" @@ -28039,10 +28248,10 @@ __metadata: languageName: node linkType: hard -"decimal.js@npm:10, decimal.js@npm:^10.3.1, decimal.js@npm:^10.4.3": - version: 10.4.3 - resolution: "decimal.js@npm:10.4.3" - checksum: 10/de663a7bc4d368e3877db95fcd5c87b965569b58d16cdc4258c063d231ca7118748738df17cd638f7e9dd0be8e34cec08d7234b20f1f2a756a52fc5a38b188d0 +"decimal.js@npm:10, decimal.js@npm:^10.4.3, decimal.js@npm:^10.6.0": + version: 10.6.0 + resolution: "decimal.js@npm:10.6.0" + checksum: 10/c0d45842d47c311d11b38ce7ccc911121953d4df3ebb1465d92b31970eb4f6738a065426a06094af59bee4b0d64e42e7c8984abd57b6767c64ea90cf90bb4a69 languageName: node linkType: hard @@ -28073,15 +28282,15 @@ __metadata: languageName: node linkType: hard -"dedent@npm:^1.0.0": - version: 1.5.3 - resolution: "dedent@npm:1.5.3" +"dedent@npm:^1.6.0": + version: 1.7.0 + resolution: "dedent@npm:1.7.0" peerDependencies: babel-plugin-macros: ^3.1.0 peerDependenciesMeta: babel-plugin-macros: optional: true - checksum: 10/e5277f6268f288649503125b781a7b7a2c9b22d011139688c0b3619fe40121e600eb1f077c891938d4b2428bdb6326cc3c77a763e4b1cc681bd9666ab1bad2a1 + checksum: 10/c902f3e7e828923bd642c12c1d8996616ff5588f8279a2951790bd7c7e479fa4dd7f016b55ce2c9ea1aa2895fc503e7d6c0cde6ebc95ca683ac0230f7c911fd7 languageName: node linkType: hard @@ -28323,19 +28532,19 @@ __metadata: languageName: node linkType: hard -"destroy@npm:1.2.0, destroy@npm:^1.0.4": +"destroy@npm:1.2.0, destroy@npm:^1.0.4, destroy@npm:~1.2.0": version: 1.2.0 resolution: "destroy@npm:1.2.0" checksum: 10/0acb300b7478a08b92d810ab229d5afe0d2f4399272045ab22affa0d99dbaf12637659411530a6fcd597a9bdac718fc94373a61a95b4651bbc7b83684a565e38 languageName: node linkType: hard -"destroyable-server@npm:^1.0.0, destroyable-server@npm:^1.0.2": - version: 1.0.2 - resolution: "destroyable-server@npm:1.0.2" +"destroyable-server@npm:^1.0.0, destroyable-server@npm:^1.1.1": + version: 1.1.1 + resolution: "destroyable-server@npm:1.1.1" dependencies: "@types/node": "npm:*" - checksum: 10/c93c7651dac530bf9dd4263eab59f5724a2453dc35658d98e3707c4c9171d8496ef404655852bef257586b31850f4f6730397bc5ff496ed799352660b0495a0a + checksum: 10/483867c0ee3adb6f265e0163ea5f204135a30cd2b14ef07f58054186f63cc12ca7c591431b30cb664216e1bdc3156d50c74b56c3e17f67a5e75b0d32f0fe84f5 languageName: node linkType: hard @@ -28360,7 +28569,7 @@ __metadata: languageName: node linkType: hard -"detect-newline@npm:^3.0.0": +"detect-newline@npm:^3.1.0": version: 3.1.0 resolution: "detect-newline@npm:3.1.0" checksum: 10/ae6cd429c41ad01b164c59ea36f264a2c479598e61cba7c99da24175a7ab80ddf066420f2bec9a1c57a6bead411b4655ff15ad7d281c000a89791f48cbe939e7 @@ -28541,6 +28750,13 @@ __metadata: languageName: node linkType: hard +"diff@npm:~8.0.2": + version: 8.0.2 + resolution: "diff@npm:8.0.2" + checksum: 10/82a2120d3418f97822e17a6044ccd4b99a91e26e145e8698353673d7146bd2d092bbebb79c112aae7badc7b9c526f9098cbe342f96174feb6beabdd2587b3c42 + languageName: node + linkType: hard + "diffie-hellman@npm:^5.0.3": version: 5.0.3 resolution: "diffie-hellman@npm:5.0.3" @@ -28584,24 +28800,12 @@ __metadata: languageName: node linkType: hard -"docker-compose@npm:^0.24.8": - version: 0.24.8 - resolution: "docker-compose@npm:0.24.8" +"docker-compose@npm:^1.3.0": + version: 1.3.0 + resolution: "docker-compose@npm:1.3.0" dependencies: yaml: "npm:^2.2.2" - checksum: 10/2b8526f9797a55c819ff2d7dcea57085b012b3a3d77bc2e1a6b45c3fc9e82196312f5298cbe8299966462454a5ac8f68814bb407736b4385e0d226a2a39e877a - languageName: node - linkType: hard - -"docker-modem@npm:^3.0.0": - version: 3.0.3 - resolution: "docker-modem@npm:3.0.3" - dependencies: - debug: "npm:^4.1.1" - readable-stream: "npm:^3.5.0" - split-ca: "npm:^1.0.1" - ssh2: "npm:^1.4.0" - checksum: 10/266030d49961a82810152150b13be459490eabb00ca319786930a31ca4a0491bf1ea89ac43b7972a7dbc807e1f73b92bb482be0682247cbf1180b58b80fc08d7 + checksum: 10/7c1b395fc104031eadef08cac0c028af11fd9e3084265dca9e17bc76ad27c2bfe96d8b74df258bb64b198f9e0f1792a7f2c63123cddc0d60bb776f211d54cfb2 languageName: node linkType: hard @@ -28617,18 +28821,7 @@ __metadata: languageName: node linkType: hard -"dockerode@npm:^3.3.5": - version: 3.3.5 - resolution: "dockerode@npm:3.3.5" - dependencies: - "@balena/dockerignore": "npm:^1.0.2" - docker-modem: "npm:^3.0.0" - tar-fs: "npm:~2.0.1" - checksum: 10/1748e8d96f88fe71bb165a4c05726904937f5863b69eaeb4a3c1bb3bbf66940c7bef13b349ff757dc43664b4367611aab76f35c1ba468f07dcbaba567e6acd88 - languageName: node - linkType: hard - -"dockerode@npm:^4.0.0": +"dockerode@npm:^4.0.0, dockerode@npm:^4.0.9": version: 4.0.9 resolution: "dockerode@npm:4.0.9" dependencies: @@ -28730,15 +28923,6 @@ __metadata: languageName: node linkType: hard -"domexception@npm:^4.0.0": - version: 4.0.0 - resolution: "domexception@npm:4.0.0" - dependencies: - webidl-conversions: "npm:^7.0.0" - checksum: 10/4ed443227d2871d76c58d852b2e93c68e0443815b2741348f20881bedee8c1ad4f9bfc5d30c7dec433cd026b57da63407c010260b1682fef4c8847e7181ea43f - languageName: node - linkType: hard - "domhandler@npm:^4.0.0, domhandler@npm:^4.2.0, domhandler@npm:^4.3.1": version: 4.3.1 resolution: "domhandler@npm:4.3.1" @@ -28757,19 +28941,7 @@ __metadata: languageName: node linkType: hard -"dompurify@npm:=3.2.4": - version: 3.2.4 - resolution: "dompurify@npm:3.2.4" - dependencies: - "@types/trusted-types": "npm:^2.0.7" - dependenciesMeta: - "@types/trusted-types": - optional: true - checksum: 10/98570c53385518a2f9b617f796926338856acfdd3369c88b5905bddf96bd7d391bf8a5433127155e0046e6faa2bfb767185fcd571b865dfabe624c099e2537f5 - languageName: node - linkType: hard - -"dompurify@npm:^3.1.7, dompurify@npm:^3.2.4": +"dompurify@npm:=3.2.6": version: 3.2.6 resolution: "dompurify@npm:3.2.6" dependencies: @@ -28781,6 +28953,18 @@ __metadata: languageName: node linkType: hard +"dompurify@npm:^3.1.7, dompurify@npm:^3.2.4": + version: 3.3.1 + resolution: "dompurify@npm:3.3.1" + dependencies: + "@types/trusted-types": "npm:^2.0.7" + dependenciesMeta: + "@types/trusted-types": + optional: true + checksum: 10/f71cca489e628591165d16e8cf4fa4f0d3e2ee48db4d73e9d2c5bedc6f915c92f9e9f101f8c4ba790bec0cdffe7f4e1747f5e31c69dc53ce7ae20a81ff6b0022 + languageName: node + linkType: hard + "domutils@npm:^2.5.2, domutils@npm:^2.8.0": version: 2.8.0 resolution: "domutils@npm:2.8.0" @@ -28920,7 +29104,7 @@ __metadata: "@backstage/create-app": "workspace:^" "@backstage/errors": "workspace:^" "@types/fs-extra": "npm:^11.0.0" - "@types/node": "npm:^20.16.0" + "@types/node": "npm:^22.13.14" chalk: "npm:^4.0.0" commander: "npm:^12.0.0" cross-fetch: "npm:^4.0.0" @@ -28954,19 +29138,6 @@ __metadata: languageName: node linkType: hard -"easy-table@npm:1.2.0": - version: 1.2.0 - resolution: "easy-table@npm:1.2.0" - dependencies: - ansi-regex: "npm:^5.0.1" - wcwidth: "npm:^1.0.1" - dependenciesMeta: - wcwidth: - optional: true - checksum: 10/0d1be7cd9419cd1b56ca5a978646b3cff241ccd8cf95bdb2742f36854084b3aef2e9af6ec14142855aa80e4cab1f4baad0f610a99c77509f23676b8330730177 - languageName: node - linkType: hard - "ebnf@npm:^1.9.1": version: 1.9.1 resolution: "ebnf@npm:1.9.1" @@ -29004,25 +29175,18 @@ __metadata: linkType: hard "elastic-builder@npm:^2.16.0": - version: 2.30.0 - resolution: "elastic-builder@npm:2.30.0" + version: 2.31.1 + resolution: "elastic-builder@npm:2.31.1" dependencies: - lodash.has: "npm:^4.5.2" - lodash.hasin: "npm:^4.5.2" - lodash.head: "npm:^4.0.1" - lodash.isempty: "npm:^4.4.0" - lodash.isnil: "npm:^4.0.0" - lodash.isobject: "npm:^3.0.2" - lodash.isstring: "npm:^4.0.1" - lodash.omit: "npm:^4.5.0" - checksum: 10/10b3e0cd3b24c680a9be6a480e4eb7869daf52ff536b56132f9b906fd577cea8eabfd5baf691768ed92cbb23454575b85ec760a72f7a743ee35b43d199749a4e + lodash: "npm:^4.17.21" + checksum: 10/8aadd17710114ddd313c9ed3d023769985257877888e466783230d7bdcc5a7747ce0542ad27802af62a0a99791099e470e2f2b83c114007c445d6d0061496719 languageName: node linkType: hard -"electron-to-chromium@npm:^1.5.41": - version: 1.5.65 - resolution: "electron-to-chromium@npm:1.5.65" - checksum: 10/9d4e5609de75fc92aeff10976fd8fa2b9b6294edb3eb7c205e51e3e5f42c3d5f6d1e0323bb52ed4831121ee617962c9a8eafff5aee5651d33bee98677940b4be +"electron-to-chromium@npm:^1.5.249": + version: 1.5.263 + resolution: "electron-to-chromium@npm:1.5.263" + checksum: 10/c74c6fedc38a0a5d823f4ed168efb6961972a30df443caa44265c85231087936673cdfbccfa0c14ba6f1a0fd6c634ef43b046fbdd3cdfce6c73523408d86caee languageName: node linkType: hard @@ -29076,6 +29240,13 @@ __metadata: languageName: node linkType: hard +"empathic@npm:^2.0.0": + version: 2.0.0 + resolution: "empathic@npm:2.0.0" + checksum: 10/90f47d93f8d1db3aa00ce1bfae2940bf76379dbb34bd562edbd92c3564a173cb1d6bd3cadb645fad0224839c25886abde801155d9b972dda6add7a5cc8b35d48 + languageName: node + linkType: hard + "enabled@npm:2.0.x": version: 2.0.0 resolution: "enabled@npm:2.0.0" @@ -29133,7 +29304,7 @@ __metadata: languageName: node linkType: hard -"enhanced-resolve@npm:^5.17.1, enhanced-resolve@npm:^5.17.3, enhanced-resolve@npm:^5.18.0": +"enhanced-resolve@npm:^5.17.3, enhanced-resolve@npm:^5.18.0": version: 5.18.3 resolution: "enhanced-resolve@npm:5.18.3" dependencies: @@ -29167,6 +29338,13 @@ __metadata: languageName: node linkType: hard +"entities@npm:^6.0.0": + version: 6.0.1 + resolution: "entities@npm:6.0.1" + checksum: 10/62af1307202884349d2867f0aac5c60d8b57102ea0b0e768b16246099512c28e239254ad772d6834e7e14cb1b6f153fc3d0c031934e3183b086c86d3838d874a + languageName: node + linkType: hard + "entities@npm:~2.1.0": version: 2.1.0 resolution: "entities@npm:2.1.0" @@ -29220,25 +29398,26 @@ __metadata: languageName: node linkType: hard -"es-abstract@npm:^1.17.5, es-abstract@npm:^1.20.4, es-abstract@npm:^1.22.1, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.6": - version: 1.23.7 - resolution: "es-abstract@npm:1.23.7" +"es-abstract@npm:^1.17.5, es-abstract@npm:^1.20.4, es-abstract@npm:^1.22.1, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.6, es-abstract@npm:^1.23.9, es-abstract@npm:^1.24.0": + version: 1.24.0 + resolution: "es-abstract@npm:1.24.0" dependencies: array-buffer-byte-length: "npm:^1.0.2" arraybuffer.prototype.slice: "npm:^1.0.4" available-typed-arrays: "npm:^1.0.7" call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.3" + call-bound: "npm:^1.0.4" data-view-buffer: "npm:^1.0.2" data-view-byte-length: "npm:^1.0.2" data-view-byte-offset: "npm:^1.0.1" es-define-property: "npm:^1.0.1" es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.0.0" - es-set-tostringtag: "npm:^2.0.3" + es-object-atoms: "npm:^1.1.1" + es-set-tostringtag: "npm:^2.1.0" es-to-primitive: "npm:^1.3.0" function.prototype.name: "npm:^1.1.8" - get-intrinsic: "npm:^1.2.6" + get-intrinsic: "npm:^1.3.0" + get-proto: "npm:^1.0.1" get-symbol-description: "npm:^1.1.0" globalthis: "npm:^1.0.4" gopd: "npm:^1.2.0" @@ -29250,18 +29429,24 @@ __metadata: is-array-buffer: "npm:^3.0.5" is-callable: "npm:^1.2.7" is-data-view: "npm:^1.0.2" + is-negative-zero: "npm:^2.0.3" is-regex: "npm:^1.2.1" + is-set: "npm:^2.0.3" is-shared-array-buffer: "npm:^1.0.4" is-string: "npm:^1.1.1" is-typed-array: "npm:^1.1.15" - is-weakref: "npm:^1.1.0" + is-weakref: "npm:^1.1.1" math-intrinsics: "npm:^1.1.0" - object-inspect: "npm:^1.13.3" + object-inspect: "npm:^1.13.4" object-keys: "npm:^1.1.1" object.assign: "npm:^4.1.7" - regexp.prototype.flags: "npm:^1.5.3" + own-keys: "npm:^1.0.1" + regexp.prototype.flags: "npm:^1.5.4" safe-array-concat: "npm:^1.1.3" + safe-push-apply: "npm:^1.0.0" safe-regex-test: "npm:^1.1.0" + set-proto: "npm:^1.0.0" + stop-iteration-iterator: "npm:^1.1.0" string.prototype.trim: "npm:^1.2.10" string.prototype.trimend: "npm:^1.0.9" string.prototype.trimstart: "npm:^1.0.8" @@ -29270,8 +29455,8 @@ __metadata: typed-array-byte-offset: "npm:^1.0.4" typed-array-length: "npm:^1.0.7" unbox-primitive: "npm:^1.1.0" - which-typed-array: "npm:^1.1.18" - checksum: 10/c079f4e3fe25af0bccf483383a2dd170d69483f93cbd34ad534da0749d3634f59f907edfee898963a9fd5c507d16336fa3d5ce77e1d5d36eae6c00d835ebd423 + which-typed-array: "npm:^1.1.19" + checksum: 10/64e07a886f7439cf5ccfc100f9716e6173e10af6071a50a5031afbdde474a3dbc9619d5965da54e55f8908746a9134a46be02af8c732d574b7b81ed3124e2daf languageName: node linkType: hard @@ -29352,7 +29537,7 @@ __metadata: languageName: node linkType: hard -"es-module-lexer@npm:^1.2.1, es-module-lexer@npm:^1.3.1": +"es-module-lexer@npm:^1.2.1, es-module-lexer@npm:^1.6.0": version: 1.7.0 resolution: "es-module-lexer@npm:1.7.0" checksum: 10/b6f3e576a3fed4d82b0d0ad4bbf6b3a5ad694d2e7ce8c4a069560da3db6399381eaba703616a182b16dde50ce998af64e07dcf49f2ae48153b9e07be3f107087 @@ -29380,12 +29565,12 @@ __metadata: languageName: node linkType: hard -"es-shim-unscopables@npm:^1.0.0, es-shim-unscopables@npm:^1.0.2": - version: 1.0.2 - resolution: "es-shim-unscopables@npm:1.0.2" +"es-shim-unscopables@npm:^1.0.2, es-shim-unscopables@npm:^1.1.0": + version: 1.1.0 + resolution: "es-shim-unscopables@npm:1.1.0" dependencies: - hasown: "npm:^2.0.0" - checksum: 10/6d3bf91f658a27cc7217cd32b407a0d714393a84d125ad576319b9e83a893bea165cf41270c29e9ceaa56d3cf41608945d7e2a2c31fd51c0009b0c31402b91c7 + hasown: "npm:^2.0.2" + checksum: 10/c351f586c30bbabc62355be49564b2435468b52c3532b8a1663672e3d10dc300197e69c247869dd173e56d86423ab95fc0c10b0939cdae597094e0fdca078cba languageName: node linkType: hard @@ -29453,31 +29638,109 @@ __metadata: linkType: hard "esbuild-loader@npm:^4.0.0": - version: 4.2.2 - resolution: "esbuild-loader@npm:4.2.2" + version: 4.4.0 + resolution: "esbuild-loader@npm:4.4.0" dependencies: - esbuild: "npm:^0.21.0" - get-tsconfig: "npm:^4.7.0" + esbuild: "npm:^0.25.0" + get-tsconfig: "npm:^4.10.1" loader-utils: "npm:^2.0.4" webpack-sources: "npm:^1.4.3" peerDependencies: webpack: ^4.40.0 || ^5.0.0 - checksum: 10/235d06c60e26827333c3c66df6e2f13fef31e1b84b8310ba36ebdb6aaa31abdf7628a940326e4923824cdb501c8ce6a6e9129c4e465daaeab112f0b5db3423f4 + checksum: 10/ec1ba490af711b63ff0b5d157aed9c28cfd1ddc3d570996376e86b296558def07e50219b4727dd4674566a18d8df4c75d468cc0188f922688fa5bb1d7c8621ce languageName: node linkType: hard -"esbuild-register@npm:^3.5.0": - version: 3.6.0 - resolution: "esbuild-register@npm:3.6.0" +"esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0, esbuild@npm:^0.27.0": + version: 0.27.1 + resolution: "esbuild@npm:0.27.1" dependencies: - debug: "npm:^4.3.4" - peerDependencies: - esbuild: ">=0.12 <1" - checksum: 10/4ae1a016e3dad5b53c3d68cf07e31d8c1cec1a0b584038ece726097ac80bd33ab48fb224c766c9b341c04793837e652461eaca9327a116e7564f553b61ccca71 + "@esbuild/aix-ppc64": "npm:0.27.1" + "@esbuild/android-arm": "npm:0.27.1" + "@esbuild/android-arm64": "npm:0.27.1" + "@esbuild/android-x64": "npm:0.27.1" + "@esbuild/darwin-arm64": "npm:0.27.1" + "@esbuild/darwin-x64": "npm:0.27.1" + "@esbuild/freebsd-arm64": "npm:0.27.1" + "@esbuild/freebsd-x64": "npm:0.27.1" + "@esbuild/linux-arm": "npm:0.27.1" + "@esbuild/linux-arm64": "npm:0.27.1" + "@esbuild/linux-ia32": "npm:0.27.1" + "@esbuild/linux-loong64": "npm:0.27.1" + "@esbuild/linux-mips64el": "npm:0.27.1" + "@esbuild/linux-ppc64": "npm:0.27.1" + "@esbuild/linux-riscv64": "npm:0.27.1" + "@esbuild/linux-s390x": "npm:0.27.1" + "@esbuild/linux-x64": "npm:0.27.1" + "@esbuild/netbsd-arm64": "npm:0.27.1" + "@esbuild/netbsd-x64": "npm:0.27.1" + "@esbuild/openbsd-arm64": "npm:0.27.1" + "@esbuild/openbsd-x64": "npm:0.27.1" + "@esbuild/openharmony-arm64": "npm:0.27.1" + "@esbuild/sunos-x64": "npm:0.27.1" + "@esbuild/win32-arm64": "npm:0.27.1" + "@esbuild/win32-ia32": "npm:0.27.1" + "@esbuild/win32-x64": "npm:0.27.1" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10/534148f01e85ca93ec3a4ae8bef133680f5659e639915cd3a453d6ec9ead94c9a2e9bfd61380301471447e182beb62841cb72e0fa18251cdce3454a2511d7cf4 languageName: node linkType: hard -"esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0, esbuild@npm:^0.25.0": +"esbuild@npm:^0.25.0": version: 0.25.8 resolution: "esbuild@npm:0.25.8" dependencies: @@ -29566,86 +29829,6 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:^0.21.0": - version: 0.21.5 - resolution: "esbuild@npm:0.21.5" - dependencies: - "@esbuild/aix-ppc64": "npm:0.21.5" - "@esbuild/android-arm": "npm:0.21.5" - "@esbuild/android-arm64": "npm:0.21.5" - "@esbuild/android-x64": "npm:0.21.5" - "@esbuild/darwin-arm64": "npm:0.21.5" - "@esbuild/darwin-x64": "npm:0.21.5" - "@esbuild/freebsd-arm64": "npm:0.21.5" - "@esbuild/freebsd-x64": "npm:0.21.5" - "@esbuild/linux-arm": "npm:0.21.5" - "@esbuild/linux-arm64": "npm:0.21.5" - "@esbuild/linux-ia32": "npm:0.21.5" - "@esbuild/linux-loong64": "npm:0.21.5" - "@esbuild/linux-mips64el": "npm:0.21.5" - "@esbuild/linux-ppc64": "npm:0.21.5" - "@esbuild/linux-riscv64": "npm:0.21.5" - "@esbuild/linux-s390x": "npm:0.21.5" - "@esbuild/linux-x64": "npm:0.21.5" - "@esbuild/netbsd-x64": "npm:0.21.5" - "@esbuild/openbsd-x64": "npm:0.21.5" - "@esbuild/sunos-x64": "npm:0.21.5" - "@esbuild/win32-arm64": "npm:0.21.5" - "@esbuild/win32-ia32": "npm:0.21.5" - "@esbuild/win32-x64": "npm:0.21.5" - dependenciesMeta: - "@esbuild/aix-ppc64": - optional: true - "@esbuild/android-arm": - optional: true - "@esbuild/android-arm64": - optional: true - "@esbuild/android-x64": - optional: true - "@esbuild/darwin-arm64": - optional: true - "@esbuild/darwin-x64": - optional: true - "@esbuild/freebsd-arm64": - optional: true - "@esbuild/freebsd-x64": - optional: true - "@esbuild/linux-arm": - optional: true - "@esbuild/linux-arm64": - optional: true - "@esbuild/linux-ia32": - optional: true - "@esbuild/linux-loong64": - optional: true - "@esbuild/linux-mips64el": - optional: true - "@esbuild/linux-ppc64": - optional: true - "@esbuild/linux-riscv64": - optional: true - "@esbuild/linux-s390x": - optional: true - "@esbuild/linux-x64": - optional: true - "@esbuild/netbsd-x64": - optional: true - "@esbuild/openbsd-x64": - optional: true - "@esbuild/sunos-x64": - optional: true - "@esbuild/win32-arm64": - optional: true - "@esbuild/win32-ia32": - optional: true - "@esbuild/win32-x64": - optional: true - bin: - esbuild: bin/esbuild - checksum: 10/d2ff2ca84d30cce8e871517374d6c2290835380dc7cd413b2d49189ed170d45e407be14de2cb4794cf76f75cf89955c4714726ebd3de7444b3046f5cab23ab6b - languageName: node - linkType: hard - "esbuild@npm:esbuild-wasm@^0.23.0": version: 0.23.1 resolution: "esbuild-wasm@npm:0.23.1" @@ -29723,7 +29906,7 @@ __metadata: languageName: node linkType: hard -"escodegen@npm:^2.0.0, escodegen@npm:^2.1.0": +"escodegen@npm:^2.1.0": version: 2.1.0 resolution: "escodegen@npm:2.1.0" dependencies: @@ -29776,15 +29959,15 @@ __metadata: languageName: node linkType: hard -"eslint-module-utils@npm:^2.12.0": - version: 2.12.0 - resolution: "eslint-module-utils@npm:2.12.0" +"eslint-module-utils@npm:^2.12.1": + version: 2.12.1 + resolution: "eslint-module-utils@npm:2.12.1" dependencies: debug: "npm:^3.2.7" peerDependenciesMeta: eslint: optional: true - checksum: 10/dd27791147eca17366afcb83f47d6825b6ce164abb256681e5de4ec1d7e87d8605641eb869298a0dbc70665e2446dbcc2f40d3e1631a9475dd64dd23d4ca5dee + checksum: 10/bd25d6610ec3abaa50e8f1beb0119541562bbb8dd02c035c7e887976fe1e0c5dd8175f4607ca8d86d1146df24d52a071bd3d1dd329f6902bd58df805a8ca16d3 languageName: node linkType: hard @@ -29803,37 +29986,37 @@ __metadata: linkType: hard "eslint-plugin-import@npm:^2.31.0": - version: 2.31.0 - resolution: "eslint-plugin-import@npm:2.31.0" + version: 2.32.0 + resolution: "eslint-plugin-import@npm:2.32.0" dependencies: "@rtsao/scc": "npm:^1.1.0" - array-includes: "npm:^3.1.8" - array.prototype.findlastindex: "npm:^1.2.5" - array.prototype.flat: "npm:^1.3.2" - array.prototype.flatmap: "npm:^1.3.2" + array-includes: "npm:^3.1.9" + array.prototype.findlastindex: "npm:^1.2.6" + array.prototype.flat: "npm:^1.3.3" + array.prototype.flatmap: "npm:^1.3.3" debug: "npm:^3.2.7" doctrine: "npm:^2.1.0" eslint-import-resolver-node: "npm:^0.3.9" - eslint-module-utils: "npm:^2.12.0" + eslint-module-utils: "npm:^2.12.1" hasown: "npm:^2.0.2" - is-core-module: "npm:^2.15.1" + is-core-module: "npm:^2.16.1" is-glob: "npm:^4.0.3" minimatch: "npm:^3.1.2" object.fromentries: "npm:^2.0.8" object.groupby: "npm:^1.0.3" - object.values: "npm:^1.2.0" + object.values: "npm:^1.2.1" semver: "npm:^6.3.1" - string.prototype.trimend: "npm:^1.0.8" + string.prototype.trimend: "npm:^1.0.9" tsconfig-paths: "npm:^3.15.0" peerDependencies: eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 - checksum: 10/6b76bd009ac2db0615d9019699d18e2a51a86cb8c1d0855a35fb1b418be23b40239e6debdc6e8c92c59f1468ed0ea8d7b85c817117a113d5cc225be8a02ad31c + checksum: 10/1bacf4967e9ebf99e12176a795f0d6d3a87d1c9a030c2207f27b267e10d96a1220be2647504c7fc13ab543cdf13ffef4b8f5620e0447032dba4ff0d3922f7c9e languageName: node linkType: hard "eslint-plugin-jest@npm:^28.9.0": - version: 28.9.0 - resolution: "eslint-plugin-jest@npm:28.9.0" + version: 28.14.0 + resolution: "eslint-plugin-jest@npm:28.14.0" dependencies: "@typescript-eslint/utils": "npm:^6.0.0 || ^7.0.0 || ^8.0.0" peerDependencies: @@ -29845,7 +30028,7 @@ __metadata: optional: true jest: optional: true - checksum: 10/399dd882205da996c2d46b768b744093346ffe4f730972825c72a3f85cc2e26bc08ccd73a7b487f8da4c39fa0178c58b571d5d611a22ced5518b5dfccb48d906 + checksum: 10/6032497bd97d6dd010450d5fdf535b8613a2789f4f83764ae04361c48d06d92f3d9b2e4350914b8fd857b6e611ba2b5282a1133ab8ec51b3e7053f9d336058e6 languageName: node linkType: hard @@ -29924,15 +30107,15 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-storybook@npm:^9.1.7": - version: 9.1.16 - resolution: "eslint-plugin-storybook@npm:9.1.16" +"eslint-plugin-storybook@npm:^10.1.9": + version: 10.1.9 + resolution: "eslint-plugin-storybook@npm:10.1.9" dependencies: "@typescript-eslint/utils": "npm:^8.8.1" peerDependencies: eslint: ">=8" - storybook: ^9.1.16 - checksum: 10/3142e70ee2a8cead5c891b76861914dc5e675d85be758171e4c63db912351e57da0ce4cbccf5a3cf7aaf9cf8fc2f40cf6110ef9a459d0c61cef287b82570e88b + storybook: ^10.1.9 + checksum: 10/65ea272cf450373d8b1f718f6c1ab878309f88b7cb2b27071b941d2b9d3402b0a102c1a83565c3bec64a9bc4a99ccbc8c25860fdf3112fed4dbfbe56361b0213 languageName: node linkType: hard @@ -29948,30 +30131,31 @@ __metadata: linkType: hard "eslint-plugin-unused-imports@npm:^4.1.4": - version: 4.1.4 - resolution: "eslint-plugin-unused-imports@npm:4.1.4" + version: 4.3.0 + resolution: "eslint-plugin-unused-imports@npm:4.3.0" peerDependencies: "@typescript-eslint/eslint-plugin": ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0 eslint: ^9.0.0 || ^8.0.0 peerDependenciesMeta: "@typescript-eslint/eslint-plugin": optional: true - checksum: 10/8e987028ad925ce1e04c01dcae70adbf44c2878a8b15c4327b33a2861e471d7fe00f6fe213fbd2b936f3fcefc8ccabb0d778aa1d6e0e0387a3dc7fe150cd4ed4 + checksum: 10/64ec1f686d90f18a27c27273a0338bad6964a611f497b81c5e7eace9d9a4f38d96070d7c389c2626095a9e5e7dbcafccc6444fa2933c9a7649996a7ca875738f languageName: node linkType: hard "eslint-rspack-plugin@npm:^4.2.1": - version: 4.2.1 - resolution: "eslint-rspack-plugin@npm:4.2.1" + version: 4.3.0 + resolution: "eslint-rspack-plugin@npm:4.3.0" dependencies: "@types/eslint": "npm:^8.56.10" jest-worker: "npm:^29.7.0" micromatch: "npm:^4.0.8" normalize-path: "npm:^3.0.0" schema-utils: "npm:^4.2.0" + tinyglobby: "npm:^0.2.15" peerDependencies: eslint: ^8.0.0 || ^9.0.0 - checksum: 10/c45639e660d13af63c58f8f80103a033f607b9d635d9315d7d859408134cc35e2e3f981805f7abdd6061ec9d8207cfc7c0d5a097df9ac8c7ccd707ca5aa14583 + checksum: 10/e312c993474cd4df180f6790359936b581391ac660769ce94b7e00e96322aedbbebc8d9132969db72b04d4588ccf40dd17c45d8f5d48df625a015a819c215ae3 languageName: node linkType: hard @@ -30002,7 +30186,7 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^4.2.0, eslint-visitor-keys@npm:^4.2.1": +"eslint-visitor-keys@npm:^4.2.1": version: 4.2.1 resolution: "eslint-visitor-keys@npm:4.2.1" checksum: 10/3ee00fc6a7002d4b0ffd9dc99e13a6a7882c557329e6c25ab254220d71e5c9c4f89dca4695352949ea678eb1f3ba912a18ef8aac0a7fe094196fd92f441bfce2 @@ -30255,14 +30439,14 @@ __metadata: languageName: node linkType: hard -"eventsource-parser@npm:^3.0.1": - version: 3.0.1 - resolution: "eventsource-parser@npm:3.0.1" - checksum: 10/2730c54c3cb47d55d2967f2ece843f9fc95d8a11c2fef6fece8d17d9080193cbe3cd9ac7b04a325977f63cbf8c1664fdd0512dec1aec601666a5c5bd8564b61f +"eventsource-parser@npm:^3.0.0, eventsource-parser@npm:^3.0.1": + version: 3.0.6 + resolution: "eventsource-parser@npm:3.0.6" + checksum: 10/febf7058b9c2168ecbb33e92711a1646e06bd1568f60b6eb6a01a8bf9f8fcd29cc8320d57247059cacf657a296280159f21306d2e3ff33309a9552b2ef889387 languageName: node linkType: hard -"eventsource@npm:^3.0.2, eventsource@npm:^3.0.6": +"eventsource@npm:^3.0.2": version: 3.0.7 resolution: "eventsource@npm:3.0.7" dependencies: @@ -30271,6 +30455,15 @@ __metadata: languageName: node linkType: hard +"eventsource@npm:^4.0.0": + version: 4.1.0 + resolution: "eventsource@npm:4.1.0" + dependencies: + eventsource-parser: "npm:^3.0.1" + checksum: 10/d23b1c2a56a1dd7344a151ca57d70ebc624972150395c1c82110bda1d2918487260e8ec76c1c79677406184ecb1847509a675d4ee5367925ef77f03c9cae594d + languageName: node + linkType: hard + "evp_bytestokey@npm:^1.0.0, evp_bytestokey@npm:^1.0.3": version: 1.0.3 resolution: "evp_bytestokey@npm:1.0.3" @@ -30466,9 +30659,9 @@ __metadata: "@backstage/plugin-search-backend-node": "workspace:^" "@backstage/plugin-signals-backend": "workspace:^" "@backstage/plugin-techdocs-backend": "workspace:^" - "@opentelemetry/auto-instrumentations-node": "npm:^0.61.0" - "@opentelemetry/exporter-prometheus": "npm:^0.54.0" - "@opentelemetry/sdk-node": "npm:^0.54.0" + "@opentelemetry/auto-instrumentations-node": "npm:^0.67.0" + "@opentelemetry/exporter-prometheus": "npm:^0.208.0" + "@opentelemetry/sdk-node": "npm:^0.208.0" example-app: "link:../app" languageName: unknown linkType: soft @@ -30488,7 +30681,7 @@ __metadata: languageName: node linkType: hard -"execa@npm:^5.0.0": +"execa@npm:^5.0.0, execa@npm:^5.1.1": version: 5.1.1 resolution: "execa@npm:5.1.1" dependencies: @@ -30536,10 +30729,10 @@ __metadata: languageName: node linkType: hard -"exit@npm:^0.1.2": - version: 0.1.2 - resolution: "exit@npm:0.1.2" - checksum: 10/387555050c5b3c10e7a9e8df5f43194e95d7737c74532c409910e585d5554eaff34960c166643f5e23d042196529daad059c292dcf1fb61b8ca878d3677f4b87 +"exit-x@npm:^0.2.2": + version: 0.2.2 + resolution: "exit-x@npm:0.2.2" + checksum: 10/ee043053e6c1e237adf5ad9c4faf9f085b606f64a4ff859e2b138fab63fe642711d00c9af452a9134c4c92c55f752e818bfabab78c24d345022db163f3137027 languageName: node linkType: hard @@ -30559,16 +30752,17 @@ __metadata: languageName: node linkType: hard -"expect@npm:>28.1.3, expect@npm:^29.0.0, expect@npm:^29.7.0": - version: 29.7.0 - resolution: "expect@npm:29.7.0" +"expect@npm:30.2.0, expect@npm:>28.1.3, expect@npm:^30.0.0": + version: 30.2.0 + resolution: "expect@npm:30.2.0" dependencies: - "@jest/expect-utils": "npm:^29.7.0" - jest-get-type: "npm:^29.6.3" - jest-matcher-utils: "npm:^29.7.0" - jest-message-util: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - checksum: 10/63f97bc51f56a491950fb525f9ad94f1916e8a014947f8d8445d3847a665b5471b768522d659f5e865db20b6c2033d2ac10f35fcbd881a4d26407a4f6f18451a + "@jest/expect-utils": "npm:30.2.0" + "@jest/get-type": "npm:30.1.0" + jest-matcher-utils: "npm:30.2.0" + jest-message-util: "npm:30.2.0" + jest-mock: "npm:30.2.0" + jest-util: "npm:30.2.0" + checksum: 10/cf98ab45ab2e9f2fb9943a3ae0097f72d63a94be179a19fd2818d8fdc3b7681d31cc8ef540606eb8dd967d9c44d73fef263a614e9de260c22943ffb122ad66fd languageName: node linkType: hard @@ -30580,11 +30774,11 @@ __metadata: linkType: hard "express-openapi-validator@npm:^5.5.8": - version: 5.5.8 - resolution: "express-openapi-validator@npm:5.5.8" + version: 5.6.0 + resolution: "express-openapi-validator@npm:5.6.0" dependencies: "@apidevtools/json-schema-ref-parser": "npm:^14.0.3" - "@types/multer": "npm:^1.4.12" + "@types/multer": "npm:^1.4.13" ajv: "npm:^8.17.1" ajv-draft-04: "npm:^1.0.0" ajv-formats: "npm:^3.0.1" @@ -30599,7 +30793,7 @@ __metadata: qs: "npm:^6.14.0" peerDependencies: express: "*" - checksum: 10/0396d4fd253fcd61725b632adcd805ac4ced940c34907775bedce39701d3b884e7a60357fa29c59d282407381c1fdcecceae6d0011684103e7ffe104038cb661 + checksum: 10/03b5d26f16d9b1cc4742b9bd87570c6429fabb856b385658c0f0b323d53d241969c1572bda068ba59e694e45ded094d085674f860665aacf0a6a820f0317cef4 languageName: node linkType: hard @@ -30645,42 +30839,42 @@ __metadata: languageName: node linkType: hard -"express@npm:^4.14.0, express@npm:^4.17.1, express@npm:^4.18.1, express@npm:^4.18.2, express@npm:^4.19.2, express@npm:^4.21.0, express@npm:^4.21.2": - version: 4.21.2 - resolution: "express@npm:4.21.2" +"express@npm:^4.14.0, express@npm:^4.21.0, express@npm:^4.21.2, express@npm:^4.22.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.7.1" - 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.12" + 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/34571c442fc8c9f2c4b442d2faa10ea1175cf8559237fc6a278f5ce6254a8ffdbeb9a15d99f77c1a9f2926ab183e3b7ba560e3261f1ad4149799e3412ab66bd1 + checksum: 10/f33c1bd0c7d36e2a1f18de9cdc176469d32f68e20258d2941b8d296ab9a4fd9011872c246391bf87714f009fac5114c832ec5ac65cbee39421f1258801eb8470 languageName: node linkType: hard @@ -30907,7 +31101,7 @@ __metadata: languageName: node linkType: hard -"fastq@npm:^1.15.0, fastq@npm:^1.6.0": +"fastq@npm:^1.6.0": version: 1.18.0 resolution: "fastq@npm:1.18.0" dependencies: @@ -30934,12 +31128,12 @@ __metadata: languageName: node linkType: hard -"fb-watchman@npm:^2.0.0": - version: 2.0.1 - resolution: "fb-watchman@npm:2.0.1" +"fb-watchman@npm:^2.0.2": + version: 2.0.2 + resolution: "fb-watchman@npm:2.0.2" dependencies: bser: "npm:2.1.1" - checksum: 10/9a03efc7d41ce3ca3d799d63505a1f7312caddf4e7737d39f2165bfe4872cbd4b87eccc9e6c57229ea08f14b4d7187896da31a7270b8da7a4aaa8fba2d3d1c42 + checksum: 10/4f95d336fb805786759e383fd7fff342ceb7680f53efcc0ef82f502eb479ce35b98e8b207b6dfdfeea0eba845862107dc73813775fc6b56b3098c6e90a2dad77 languageName: node linkType: hard @@ -30974,6 +31168,15 @@ __metadata: languageName: node linkType: hard +"fd-package-json@npm:^2.0.0": + version: 2.0.0 + resolution: "fd-package-json@npm:2.0.0" + dependencies: + walk-up-path: "npm:^4.0.0" + checksum: 10/e595a1a23f8e208815cdcf26c92218240da00acce80468324408dc4a5cb6c26b6efb5076f0458a02f044562a1e60253731187a627d5416b4961468ddfc0ae426 + languageName: node + linkType: hard + "fdir@npm:^6.5.0": version: 6.5.0 resolution: "fdir@npm:6.5.0" @@ -31126,21 +31329,6 @@ __metadata: languageName: node linkType: hard -"finalhandler@npm:1.3.1": - version: 1.3.1 - resolution: "finalhandler@npm:1.3.1" - dependencies: - debug: "npm:2.6.9" - encodeurl: "npm:~2.0.0" - escape-html: "npm:~1.0.3" - on-finished: "npm:2.4.1" - parseurl: "npm:~1.3.3" - statuses: "npm:2.0.1" - unpipe: "npm:~1.0.0" - checksum: 10/4babe72969b7373b5842bc9f75c3a641a4d0f8eb53af6b89fa714d4460ce03fb92b28de751d12ba415e96e7e02870c436d67412120555e2b382640535697305b - languageName: node - linkType: hard - "finalhandler@npm:^2.1.0": version: 2.1.0 resolution: "finalhandler@npm:2.1.0" @@ -31155,6 +31343,21 @@ __metadata: languageName: node linkType: hard +"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" + parseurl: "npm:~1.3.3" + statuses: "npm:~2.0.2" + unpipe: "npm:~1.0.0" + checksum: 10/6cb4f9f80eaeb5a0fac4fdbd27a65d39271f040a0034df16556d896bfd855fd42f09da886781b3102117ea8fceba97b903c1f8b08df1fb5740576d5e0f481eed + languageName: node + linkType: hard + "find-cache-dir@npm:^2.0.0": version: 2.1.0 resolution: "find-cache-dir@npm:2.1.0" @@ -31256,17 +31459,6 @@ __metadata: languageName: node linkType: hard -"find-up@npm:^7.0.0": - version: 7.0.0 - resolution: "find-up@npm:7.0.0" - dependencies: - locate-path: "npm:^7.2.0" - path-exists: "npm:^5.0.0" - unicorn-magic: "npm:^0.1.0" - checksum: 10/7e6b08fbc05a10677e25e74bb0a020054a86b31d1806c5e6a9e32e75472bbf177210bc16e5f97453be8bda7ae2e3d97669dbb2901f8c30b39ce53929cbea6746 - languageName: node - linkType: hard - "find-yarn-workspace-root2@npm:1.2.16": version: 1.2.16 resolution: "find-yarn-workspace-root2@npm:1.2.16" @@ -31380,8 +31572,8 @@ __metadata: linkType: hard "fork-ts-checker-webpack-plugin@npm:^6.5.0": - version: 6.5.0 - resolution: "fork-ts-checker-webpack-plugin@npm:6.5.0" + version: 6.5.3 + resolution: "fork-ts-checker-webpack-plugin@npm:6.5.3" dependencies: "@babel/code-frame": "npm:^7.8.3" "@types/json-schema": "npm:^7.0.5" @@ -31406,17 +31598,17 @@ __metadata: optional: true vue-template-compiler: optional: true - checksum: 10/165af8617ff639781d0da76de9899bfcfdf9db4758d8f1dde435aa81efcf963ff776bb5b16f2f371d919faaba7d1037e046ec985d2b2bae4031c2c10eae9b6c9 + checksum: 10/415263839afe11c291be60e3335ece3ccdc80c5e0d91eeecf0d3060cfb72c7b0cb33be326dd24b325939357d53215e10c41e8187edb5db8a08fe9aaa8aa6c510 languageName: node linkType: hard "fork-ts-checker-webpack-plugin@npm:^9.0.0": - version: 9.0.2 - resolution: "fork-ts-checker-webpack-plugin@npm:9.0.2" + version: 9.1.0 + resolution: "fork-ts-checker-webpack-plugin@npm:9.1.0" dependencies: "@babel/code-frame": "npm:^7.16.7" chalk: "npm:^4.1.2" - chokidar: "npm:^3.5.3" + chokidar: "npm:^4.0.1" cosmiconfig: "npm:^8.2.0" deepmerge: "npm:^4.2.2" fs-extra: "npm:^10.0.0" @@ -31429,7 +31621,7 @@ __metadata: peerDependencies: typescript: ">3.6.0" webpack: ^5.11.0 - checksum: 10/3399ea114d9397da5e51baaa1cf2ca704f4616c8255df672889cafc2a823f77e8bf9913133852d29d676657b1d40e4a2fba8eb9fafee09e00faeda924289220e + checksum: 10/1d24387224f7d49a17f7e44c9150971172f34ae30c4b1f581b8af967e73e8f36a434ed56f78aa45fd8cf0833c73a1b020102cc61070d7dc630b70c21c9770a1b languageName: node linkType: hard @@ -31481,6 +31673,17 @@ __metadata: languageName: node linkType: hard +"formatly@npm:^0.3.0": + version: 0.3.0 + resolution: "formatly@npm:0.3.0" + dependencies: + fd-package-json: "npm:^2.0.0" + bin: + formatly: bin/index.mjs + checksum: 10/0e5a9cbb826d93171b00c283e20e6a564a16e7bc3839e695790347a1f23e3536a88d613f5cabd07403d60b7bdffe179987c88b1fc2900a9be49eea01ffbe4244 + languageName: node + linkType: hard + "formdata-node@npm:^4.3.2, formdata-node@npm:^4.3.3": version: 4.4.1 resolution: "formdata-node@npm:4.4.1" @@ -31598,7 +31801,7 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:11.3.2, fs-extra@npm:^11.0.0, fs-extra@npm:^11.1.0, fs-extra@npm:^11.2.0": +"fs-extra@npm:11.3.2, fs-extra@npm:^11.0.0, fs-extra@npm:^11.1.0, fs-extra@npm:^11.2.0, fs-extra@npm:~11.3.0": version: 11.3.2 resolution: "fs-extra@npm:11.3.2" dependencies: @@ -31632,7 +31835,7 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:^7.0.1, fs-extra@npm:~7.0.1": +"fs-extra@npm:^7.0.1": version: 7.0.1 resolution: "fs-extra@npm:7.0.1" dependencies: @@ -31703,7 +31906,7 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:^2.3.2, fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": +"fsevents@npm:^2.3.3, fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": version: 2.3.3 resolution: "fsevents@npm:2.3.3" dependencies: @@ -31722,7 +31925,7 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": +"fsevents@patch:fsevents@npm%3A^2.3.3#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": version: 2.3.3 resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" dependencies: @@ -31813,7 +32016,7 @@ __metadata: languageName: node linkType: hard -"gaxios@npm:^7.0.0, gaxios@npm:^7.0.0-rc.4, gaxios@npm:^7.1.2": +"gaxios@npm:^7.0.0, gaxios@npm:^7.0.0-rc.4, gaxios@npm:^7.1.3": version: 7.1.3 resolution: "gaxios@npm:7.1.3" dependencies: @@ -31948,6 +32151,13 @@ __metadata: languageName: node linkType: hard +"get-port@npm:^7.1.0": + version: 7.1.0 + resolution: "get-port@npm:7.1.0" + checksum: 10/f4d23b43026124007663a899578cc87ff37bfcf645c5c72651e9810ebafc759857784e409fb8e0ada9b90e5c5db089b0ae2f5f6b49fba1ce2e0aff86094ab17d + languageName: node + linkType: hard + "get-proto@npm:^1.0.1": version: 1.0.1 resolution: "get-proto@npm:1.0.1" @@ -32018,12 +32228,12 @@ __metadata: languageName: node linkType: hard -"get-tsconfig@npm:^4.7.0, get-tsconfig@npm:^4.7.2": - version: 4.7.3 - resolution: "get-tsconfig@npm:4.7.3" +"get-tsconfig@npm:^4.10.0, get-tsconfig@npm:^4.10.1": + version: 4.13.0 + resolution: "get-tsconfig@npm:4.13.0" dependencies: resolve-pkg-maps: "npm:^1.0.0" - checksum: 10/7397bb4f8aef936df4d9016555b662dcf5279f3c46428b7c7c1ff5e94ab2b87d018b3dda0f4bc1a28b154d5affd0eac5d014511172c085fd8a9cdff9ea7fe043 + checksum: 10/3603c6da30e312636e4c20461e779114c9126601d1eca70ee4e36e3e3c00e3c21892d2d920027333afa2cc9e20998a436b14abe03a53cde40742581cb0e9ceb2 languageName: node linkType: hard @@ -32115,6 +32325,15 @@ __metadata: languageName: node linkType: hard +"glob-to-regex.js@npm:^1.0.1": + version: 1.2.0 + resolution: "glob-to-regex.js@npm:1.2.0" + peerDependencies: + tslib: 2 + checksum: 10/13034e642db479d75448bdd9f37de7451bef2879c394bfe3f8df6588e0479893e94059eaee77cdf50dce675607fb2395c132dcca0c9a559a6192e89b2ad0f134 + languageName: node + linkType: hard + "glob-to-regexp@npm:^0.4.1": version: 0.4.1 resolution: "glob-to-regexp@npm:0.4.1" @@ -32122,19 +32341,14 @@ __metadata: languageName: node linkType: hard -"glob@npm:12.0.0": - version: 12.0.0 - resolution: "glob@npm:12.0.0" +"glob@npm:13.0.0": + version: 13.0.0 + resolution: "glob@npm:13.0.0" dependencies: - foreground-child: "npm:^3.3.1" - jackspeak: "npm:^4.1.1" minimatch: "npm:^10.1.1" minipass: "npm:^7.1.2" - package-json-from-dist: "npm:^1.0.0" path-scurry: "npm:^2.0.0" - bin: - glob: dist/esm/bin.mjs - checksum: 10/6e21b3f1f1fa635836d45e54bbe50704884cc3e310e0cc011cfb5429db65a030e12936d99b07e66236370efe45dc8c8b26fa5334dbf555d6f8709e0315c77c30 + checksum: 10/de390721d29ee1c9ea41e40ec2aa0de2cabafa68022e237dc4297665a5e4d650776f2573191984ea1640aba1bf0ea34eddef2d8cbfbfc2ad24b5fb0af41d8846 languageName: node linkType: hard @@ -32154,7 +32368,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^11.0.1": +"glob@npm:^11.0.1, glob@npm:^11.1.0": version: 11.1.0 resolution: "glob@npm:11.1.0" dependencies: @@ -32352,7 +32566,7 @@ __metadata: languageName: node linkType: hard -"google-auth-library@npm:^10.0.0-rc.1, google-auth-library@npm:^10.4.0": +"google-auth-library@npm:^10.0.0-rc.1, google-auth-library@npm:^10.5.0": version: 10.5.0 resolution: "google-auth-library@npm:10.5.0" dependencies: @@ -32591,16 +32805,16 @@ __metadata: linkType: hard "graphql@npm:^14.0.2 || ^15.5": - version: 15.9.0 - resolution: "graphql@npm:15.9.0" - checksum: 10/ce1f50672bcb369395d07a47048bcbb429ed1ce06dbcafb7a0999df791cb7aa7206be21497907973dbc8a01df3cd7f632f43c583f248538f186f5adfa1a0d1c5 + version: 15.10.1 + resolution: "graphql@npm:15.10.1" + checksum: 10/49177f52c1fbac022866d39bb15040122da4c3ccd12644233b20edfcb1f76aa92aa92f2a82af4668101d8f726112fda111cb11a12feb05635cd689443a7aafb8 languageName: node linkType: hard "graphql@npm:^16.0.0, graphql@npm:^16.8.1": - version: 16.10.0 - resolution: "graphql@npm:16.10.0" - checksum: 10/d42cf81ddcf3a61dfb213217576bf33c326f15b02c4cee369b373dc74100cbdcdc4479b3b797e79b654dabd8fddf50ef65ff75420e9ce5596c02e21f24c9126a + version: 16.12.0 + resolution: "graphql@npm:16.12.0" + checksum: 10/e299bc97cca178e549c8c1ed4cb164f631f07be987d3657f76cdf18c0250040cc0d456d4b6d41c87b855cac97b15a62ed345557527efcb0546492895a893bb87 languageName: node linkType: hard @@ -32817,6 +33031,15 @@ __metadata: languageName: node linkType: hard +"hast-util-parse-selector@npm:^4.0.0": + version: 4.0.0 + resolution: "hast-util-parse-selector@npm:4.0.0" + dependencies: + "@types/hast": "npm:^3.0.0" + checksum: 10/76087670d3b0b50b23a6cb70bca53a6176d6608307ccdbb3ed18b650b82e7c3513bfc40348f1389dc0c5ae872b9a768851f4335f44654abd7deafd6974c52402 + languageName: node + linkType: hard + "hast-util-whitespace@npm:^2.0.0": version: 2.0.0 resolution: "hast-util-whitespace@npm:2.0.0" @@ -32837,6 +33060,19 @@ __metadata: languageName: node linkType: hard +"hastscript@npm:^9.0.0": + version: 9.0.1 + resolution: "hastscript@npm:9.0.1" + dependencies: + "@types/hast": "npm:^3.0.0" + comma-separated-tokens: "npm:^2.0.0" + hast-util-parse-selector: "npm:^4.0.0" + property-information: "npm:^7.0.0" + space-separated-tokens: "npm:^2.0.0" + checksum: 10/9aa8135faf0307807cca4075bef4e3403ae1ce959ad4b9e6720892ba957d58ff98b2f60b5eb3ac67d88ae897dc918997299cd4249d7ac602a0066dd46442c5d4 + languageName: node + linkType: hard + "he@npm:1.2.0, he@npm:^1.2.0": version: 1.2.0 resolution: "he@npm:1.2.0" @@ -32868,9 +33104,9 @@ __metadata: linkType: hard "helmet@npm:^6.0.0": - version: 6.0.1 - resolution: "helmet@npm:6.0.1" - checksum: 10/0eec311e1a3820e206226563e55d7115fa66e9a34922229472c1dac407c5d00f0d5c4f5f7afed714fba40287aac1c30dcbfa9b3c096f1146f9d6a55c4527d65d + version: 6.2.0 + resolution: "helmet@npm:6.2.0" + checksum: 10/f112fcd0d8494e6c8ad10e9307e182f1be9c9c4917a3f9a3718c13ae120d4c4e1f251e735297d6a9266e068dcc0463ab101c8d7f2b809c0ceabcef4681f81a2a languageName: node linkType: hard @@ -32982,15 +33218,6 @@ __metadata: languageName: node linkType: hard -"html-encoding-sniffer@npm:^3.0.0": - version: 3.0.0 - resolution: "html-encoding-sniffer@npm:3.0.0" - dependencies: - whatwg-encoding: "npm:^2.0.0" - checksum: 10/707a812ec2acaf8bb5614c8618dc81e2fb6b4399d03e95ff18b65679989a072f4e919b9bef472039301a1bbfba64063ba4c79ea6e851c653ac9db80dbefe8fe5 - languageName: node - linkType: hard - "html-encoding-sniffer@npm:^4.0.0": version: 4.0.0 resolution: "html-encoding-sniffer@npm:4.0.0" @@ -33032,8 +33259,8 @@ __metadata: linkType: hard "html-webpack-plugin@npm:^5.6.3": - version: 5.6.4 - resolution: "html-webpack-plugin@npm:5.6.4" + version: 5.6.5 + resolution: "html-webpack-plugin@npm:5.6.5" dependencies: "@types/html-minifier-terser": "npm:^6.0.0" html-minifier-terser: "npm:^6.0.2" @@ -33048,7 +33275,7 @@ __metadata: optional: true webpack: optional: true - checksum: 10/781074dd8bacf48236911462201fa440c5f79525665ce5de0c35f262a323765e103caab32a7466db262c4af3d98e869ae6a9b484e967345d85aff80d25a4ca79 + checksum: 10/89e84f6ede068242348b2c2f09da67bf39a6b22d14399eaf16e399be05a4e2bb1f3f1c71ec03b7193aed96ebe2cf61d45a70565408b271661f7ea8635b237701 languageName: node linkType: hard @@ -33110,7 +33337,7 @@ __metadata: languageName: node linkType: hard -"http-errors@npm:2.0.0, http-errors@npm:^2.0.0": +"http-errors@npm:2.0.0": version: 2.0.0 resolution: "http-errors@npm:2.0.0" dependencies: @@ -33136,6 +33363,19 @@ __metadata: languageName: node linkType: hard +"http-errors@npm:^2.0.0, 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-errors@npm:~1.6.2": version: 1.6.3 resolution: "http-errors@npm:1.6.3" @@ -33308,9 +33548,9 @@ __metadata: linkType: hard "humanize-duration@npm:^3.25.1": - version: 3.32.2 - resolution: "humanize-duration@npm:3.32.2" - checksum: 10/2dfe847085c7586d2cc83613b8643a4cca70966a16522e3786be07f0283087502e5340e98a79867c7b019ea22ae85c3fa9d1ca1be587eb19ccf03710c986d41d + version: 3.33.2 + resolution: "humanize-duration@npm:3.33.2" + checksum: 10/ac6bc74b095d53e6ff320126d64c0998de213d0e38d0996f23389603c034c8f825915d4318fc19cfdb0612c4963d72d200da43cd7c28ac865bbf9a61f53418f4 languageName: node linkType: hard @@ -33355,15 +33595,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, iconv-lite@npm:^0.6.3": version: 0.6.3 resolution: "iconv-lite@npm:0.6.3" @@ -33382,6 +33613,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-replace-symbols@npm:^1.1.0": version: 1.1.0 resolution: "icss-replace-symbols@npm:1.1.0" @@ -33448,13 +33688,20 @@ __metadata: languageName: node linkType: hard -"ignore@npm:^5.1.4, ignore@npm:^5.1.8, ignore@npm:^5.2.0, ignore@npm:^5.2.4, ignore@npm:^5.3.1": +"ignore@npm:^5.1.4, ignore@npm:^5.2.0, ignore@npm:^5.2.4": version: 5.3.2 resolution: "ignore@npm:5.3.2" checksum: 10/cceb6a457000f8f6a50e1196429750d782afce5680dd878aa4221bd79972d68b3a55b4b1458fc682be978f4d3c6a249046aa0880637367216444ab7b014cfc98 languageName: node linkType: hard +"ignore@npm:^7.0.0": + version: 7.0.5 + resolution: "ignore@npm:7.0.5" + checksum: 10/f134b96a4de0af419196f52c529d5c6120c4456ff8a6b5a14ceaaa399f883e15d58d2ce651c9b69b9388491d4669dda47285d307e827de9304a53a1824801bc6 + languageName: node + linkType: hard + "immediate@npm:~3.0.5": version: 3.0.6 resolution: "immediate@npm:3.0.6" @@ -33504,15 +33751,15 @@ __metadata: languageName: node linkType: hard -"import-in-the-middle@npm:^1.8.1": - version: 1.11.0 - resolution: "import-in-the-middle@npm:1.11.0" +"import-in-the-middle@npm:^2.0.0": + version: 2.0.0 + resolution: "import-in-the-middle@npm:2.0.0" dependencies: - acorn: "npm:^8.8.2" + acorn: "npm:^8.14.0" acorn-import-attributes: "npm:^1.9.5" cjs-module-lexer: "npm:^1.2.2" module-details-from-path: "npm:^1.0.3" - checksum: 10/e6f79c9de3f1c1907856fb48b99cd2273c5f9d78eb72124ddd142382e41b6bdf1f64c028ced9e5dbfd015f282e6e3b48bd1f53dd0452e2f0a26436ee42b005d8 + checksum: 10/badb8359552f1e9fedc8569299dd1937e802256ce0fe6aa9cb348bca6f217f06e16a3ca46f889bfcb66028a096a1956674d257de9e809db4271ca0e508521c30 languageName: node linkType: hard @@ -33530,15 +33777,15 @@ __metadata: languageName: node linkType: hard -"import-local@npm:^3.0.2": - version: 3.0.2 - resolution: "import-local@npm:3.0.2" +"import-local@npm:^3.2.0": + version: 3.2.0 + resolution: "import-local@npm:3.2.0" dependencies: pkg-dir: "npm:^4.2.0" resolve-cwd: "npm:^3.0.0" bin: import-local-fixture: fixtures/cli.js - checksum: 10/c74d9f9484c878cda1de3434613c7ff72d5dadcf20e5482542232d7c2575b713ff88701d6675fcf09a3684cb23fb407c8b333b9cbc59438712723d058d8e976c + checksum: 10/0b0b0b412b2521739fbb85eeed834a3c34de9bc67e670b3d0b86248fc460d990a7b116ad056c084b87a693ef73d1f17268d6a5be626bb43c998a8b1c8a230004 languageName: node linkType: hard @@ -33885,13 +34132,6 @@ __metadata: languageName: node linkType: hard -"is-arrayish@npm:^0.3.1": - version: 0.3.2 - resolution: "is-arrayish@npm:0.3.2" - checksum: 10/81a78d518ebd8b834523e25d102684ee0f7e98637136d3bdc93fd09636350fa06f1d8ca997ea28143d4d13cb1b69c0824f082db0ac13e1ab3311c10ffea60ade - languageName: node - linkType: hard - "is-async-function@npm:^2.0.0": version: 2.0.0 resolution: "is-async-function@npm:2.0.0" @@ -33963,7 +34203,7 @@ __metadata: languageName: node linkType: hard -"is-core-module@npm:^2.13.0, is-core-module@npm:^2.15.1, is-core-module@npm:^2.16.0": +"is-core-module@npm:^2.13.0, is-core-module@npm:^2.16.0, is-core-module@npm:^2.16.1": version: 2.16.1 resolution: "is-core-module@npm:2.16.1" dependencies: @@ -34081,7 +34321,7 @@ __metadata: languageName: node linkType: hard -"is-generator-fn@npm:^2.0.0": +"is-generator-fn@npm:^2.1.0": version: 2.1.0 resolution: "is-generator-fn@npm:2.1.0" checksum: 10/a6ad5492cf9d1746f73b6744e0c43c0020510b59d56ddcb78a91cbc173f09b5e6beff53d75c9c5a29feb618bfef2bf458e025ecf3a57ad2268e2fb2569f56215 @@ -34186,6 +34426,13 @@ __metadata: languageName: node linkType: hard +"is-negative-zero@npm:^2.0.3": + version: 2.0.3 + resolution: "is-negative-zero@npm:2.0.3" + checksum: 10/8fe5cffd8d4fb2ec7b49d657e1691889778d037494c6f40f4d1a524cadd658b4b53ad7b6b73a59bcb4b143ae9a3d15829af864b2c0f9d65ac1e678c4c80f17e5 + languageName: node + linkType: hard + "is-network-error@npm:^1.0.0": version: 1.0.1 resolution: "is-network-error@npm:1.0.1" @@ -34523,12 +34770,12 @@ __metadata: languageName: node linkType: hard -"is-weakref@npm:^1.0.2, is-weakref@npm:^1.1.0": - version: 1.1.0 - resolution: "is-weakref@npm:1.1.0" +"is-weakref@npm:^1.0.2, is-weakref@npm:^1.1.1": + version: 1.1.1 + resolution: "is-weakref@npm:1.1.1" dependencies: - call-bound: "npm:^1.0.2" - checksum: 10/89e627cc1763ea110574bb408fcf060ede47e70437d9278858bc939e3b3f7e4b7c558610b733da5f2ad6084d9f12b9c714b011ccf3fa771ec87e221c22bed910 + call-bound: "npm:^1.0.3" + checksum: 10/543506fd8259038b371bb083aac25b16cb4fd8b12fc58053aa3d45ac28dfd001cd5c6dffbba7aeea4213c74732d46b6cb2cfb5b412eed11f2db524f3f97d09a0 languageName: node linkType: hard @@ -34630,13 +34877,13 @@ __metadata: languageName: node linkType: hard -"isolated-vm@npm:^5.0.1": - version: 5.0.4 - resolution: "isolated-vm@npm:5.0.4" +"isolated-vm@npm:^6.0.1": + version: 6.0.2 + resolution: "isolated-vm@npm:6.0.2" dependencies: node-gyp: "npm:latest" - prebuild-install: "npm:^7.1.2" - checksum: 10/f48e69ecf907645711d0a372cb6adb28cf72499e34b6e008ed597994bfd90d41dd11dc478a41fc21a25aaef424ab5a95a372286e4daf7f61e231d028c0fd64ec + prebuild-install: "npm:^7.1.3" + checksum: 10/74e97f13678023bf81141a6fb5c91bc179073a024e7f0a568af60d876b781b15b11e02d4012558e7d583e38a553ccccff70fd02645ed5d7bed2150dc3921fa64 languageName: node linkType: hard @@ -34652,8 +34899,8 @@ __metadata: linkType: hard "isomorphic-git@npm:^1.23.0": - version: 1.27.2 - resolution: "isomorphic-git@npm:1.27.2" + version: 1.35.1 + resolution: "isomorphic-git@npm:1.35.1" dependencies: async-lock: "npm:^1.4.1" clean-git-ref: "npm:^2.0.1" @@ -34662,14 +34909,13 @@ __metadata: ignore: "npm:^5.1.4" minimisted: "npm:^2.0.0" pako: "npm:^1.0.10" - path-browserify: "npm:^1.0.1" pify: "npm:^4.0.1" - readable-stream: "npm:^3.4.0" - sha.js: "npm:^2.4.9" + readable-stream: "npm:^4.0.0" + sha.js: "npm:^2.4.12" simple-get: "npm:^4.0.1" bin: isogit: cli.cjs - checksum: 10/da4fa5ae6180e4a528b84f24ad4144c1a7587d5e79ed3f323b936809dc23b133615b96d7ecbb363009a30a2c9518bdf5efd572f918ddad801edc81693ddbb175 + checksum: 10/8f9244ef09a02c2779aaf682b8db40b208860d57f1bf9deed3c35641a87221f56e490b3f94178579f95d46e6a4e1bb593590994d8138649b32d60d03f9e1f5f8 languageName: node linkType: hard @@ -34714,29 +34960,16 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-instrument@npm:^5.0.4": - version: 5.1.0 - resolution: "istanbul-lib-instrument@npm:5.1.0" +"istanbul-lib-instrument@npm:^6.0.0, istanbul-lib-instrument@npm:^6.0.2": + version: 6.0.3 + resolution: "istanbul-lib-instrument@npm:6.0.3" dependencies: - "@babel/core": "npm:^7.12.3" - "@babel/parser": "npm:^7.14.7" - "@istanbuljs/schema": "npm:^0.1.2" - istanbul-lib-coverage: "npm:^3.2.0" - semver: "npm:^6.3.0" - checksum: 10/7447ba3f8049f331d5b4a1c450183e88c2fdad044149ad0d9830f71bc8da90d841c393b830bc33237ae75122c3b0e03ca845701873d6c51690bc25caa1f13a94 - languageName: node - linkType: hard - -"istanbul-lib-instrument@npm:^6.0.0": - version: 6.0.1 - resolution: "istanbul-lib-instrument@npm:6.0.1" - dependencies: - "@babel/core": "npm:^7.12.3" - "@babel/parser": "npm:^7.14.7" - "@istanbuljs/schema": "npm:^0.1.2" + "@babel/core": "npm:^7.23.9" + "@babel/parser": "npm:^7.23.9" + "@istanbuljs/schema": "npm:^0.1.3" istanbul-lib-coverage: "npm:^3.2.0" semver: "npm:^7.5.4" - checksum: 10/95fd8c66e586840989cb3c7819c6da66c4742a6fedbf16b51a5c7f1898941ad07b79ddff020f479d3a1d76743ecdbf255d93c35221875687477d4b118026e7e7 + checksum: 10/aa5271c0008dfa71b6ecc9ba1e801bf77b49dc05524e8c30d58aaf5b9505e0cd12f25f93165464d4266a518c5c75284ecb598fbd89fec081ae77d2c9d3327695 languageName: node linkType: hard @@ -34751,14 +34984,14 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-source-maps@npm:^4.0.0": - version: 4.0.0 - resolution: "istanbul-lib-source-maps@npm:4.0.0" +"istanbul-lib-source-maps@npm:^5.0.0": + version: 5.0.6 + resolution: "istanbul-lib-source-maps@npm:5.0.6" dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.23" debug: "npm:^4.1.1" istanbul-lib-coverage: "npm:^3.0.0" - source-map: "npm:^0.6.1" - checksum: 10/765252abc6b5c9d29905fc97ce04b92da87d198f2c0161e62fe0aac8bb74fb7bd472a5e1d90fe3e78723d8cad43913f08d8eefa0339536fcc33b3a1922cf5fc3 + checksum: 10/569dd0a392ee3464b1fe1accbaef5cc26de3479eacb5b91d8c67ebb7b425d39fd02247d85649c3a0e9c29b600809fa60b5af5a281a75a89c01f385b1e24823a2 languageName: node linkType: hard @@ -34853,106 +35086,110 @@ __metadata: languageName: node linkType: hard -"jest-changed-files@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-changed-files@npm:29.7.0" +"jest-changed-files@npm:30.2.0": + version: 30.2.0 + resolution: "jest-changed-files@npm:30.2.0" dependencies: - execa: "npm:^5.0.0" - jest-util: "npm:^29.7.0" + execa: "npm:^5.1.1" + jest-util: "npm:30.2.0" p-limit: "npm:^3.1.0" - checksum: 10/3d93742e56b1a73a145d55b66e96711fbf87ef89b96c2fab7cfdfba8ec06612591a982111ca2b712bb853dbc16831ec8b43585a2a96b83862d6767de59cbf83d + checksum: 10/ff2275ed5839b88c12ffa66fdc5c17ba02d3e276be6b558bed92872c282d050c3fdd1a275a81187cbe35c16d6d40337b85838772836463c7a2fbd1cba9785ca0 languageName: node linkType: hard -"jest-circus@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-circus@npm:29.7.0" +"jest-circus@npm:30.2.0": + version: 30.2.0 + resolution: "jest-circus@npm:30.2.0" dependencies: - "@jest/environment": "npm:^29.7.0" - "@jest/expect": "npm:^29.7.0" - "@jest/test-result": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" + "@jest/environment": "npm:30.2.0" + "@jest/expect": "npm:30.2.0" + "@jest/test-result": "npm:30.2.0" + "@jest/types": "npm:30.2.0" "@types/node": "npm:*" - chalk: "npm:^4.0.0" + chalk: "npm:^4.1.2" co: "npm:^4.6.0" - dedent: "npm:^1.0.0" - is-generator-fn: "npm:^2.0.0" - jest-each: "npm:^29.7.0" - jest-matcher-utils: "npm:^29.7.0" - jest-message-util: "npm:^29.7.0" - jest-runtime: "npm:^29.7.0" - jest-snapshot: "npm:^29.7.0" - jest-util: "npm:^29.7.0" + dedent: "npm:^1.6.0" + is-generator-fn: "npm:^2.1.0" + jest-each: "npm:30.2.0" + jest-matcher-utils: "npm:30.2.0" + jest-message-util: "npm:30.2.0" + jest-runtime: "npm:30.2.0" + jest-snapshot: "npm:30.2.0" + jest-util: "npm:30.2.0" p-limit: "npm:^3.1.0" - pretty-format: "npm:^29.7.0" - pure-rand: "npm:^6.0.0" + pretty-format: "npm:30.2.0" + pure-rand: "npm:^7.0.0" slash: "npm:^3.0.0" - stack-utils: "npm:^2.0.3" - checksum: 10/716a8e3f40572fd0213bcfc1da90274bf30d856e5133af58089a6ce45089b63f4d679bd44e6be9d320e8390483ebc3ae9921981993986d21639d9019b523123d + stack-utils: "npm:^2.0.6" + checksum: 10/68bfc65d92385db1017643988215e4ff5af0b10bcab86fb749a063be6bb7d5eb556dc53dd21bedf833a19aa6ae1a781a8d27b2bea25562de02d294b3017435a9 languageName: node linkType: hard -"jest-cli@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-cli@npm:29.7.0" +"jest-cli@npm:30.2.0": + version: 30.2.0 + resolution: "jest-cli@npm:30.2.0" dependencies: - "@jest/core": "npm:^29.7.0" - "@jest/test-result": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - chalk: "npm:^4.0.0" - create-jest: "npm:^29.7.0" - exit: "npm:^0.1.2" - import-local: "npm:^3.0.2" - jest-config: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - jest-validate: "npm:^29.7.0" - yargs: "npm:^17.3.1" + "@jest/core": "npm:30.2.0" + "@jest/test-result": "npm:30.2.0" + "@jest/types": "npm:30.2.0" + chalk: "npm:^4.1.2" + exit-x: "npm:^0.2.2" + import-local: "npm:^3.2.0" + jest-config: "npm:30.2.0" + jest-util: "npm:30.2.0" + jest-validate: "npm:30.2.0" + yargs: "npm:^17.7.2" peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 peerDependenciesMeta: node-notifier: optional: true bin: - jest: bin/jest.js - checksum: 10/6cc62b34d002c034203065a31e5e9a19e7c76d9e8ef447a6f70f759c0714cb212c6245f75e270ba458620f9c7b26063cd8cf6cd1f7e3afd659a7cc08add17307 + jest: ./bin/jest.js + checksum: 10/1cc8304f0e2608801c84cdecce9565a6178f668a6475aed3767a1d82cc539915f98e7404d7c387510313684011dc3095c15397d6725f73aac80fbd96c4155faa languageName: node linkType: hard -"jest-config@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-config@npm:29.7.0" +"jest-config@npm:30.2.0": + version: 30.2.0 + resolution: "jest-config@npm:30.2.0" dependencies: - "@babel/core": "npm:^7.11.6" - "@jest/test-sequencer": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - babel-jest: "npm:^29.7.0" - chalk: "npm:^4.0.0" - ci-info: "npm:^3.2.0" - deepmerge: "npm:^4.2.2" - glob: "npm:^7.1.3" - graceful-fs: "npm:^4.2.9" - jest-circus: "npm:^29.7.0" - jest-environment-node: "npm:^29.7.0" - jest-get-type: "npm:^29.6.3" - jest-regex-util: "npm:^29.6.3" - jest-resolve: "npm:^29.7.0" - jest-runner: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - jest-validate: "npm:^29.7.0" - micromatch: "npm:^4.0.4" + "@babel/core": "npm:^7.27.4" + "@jest/get-type": "npm:30.1.0" + "@jest/pattern": "npm:30.0.1" + "@jest/test-sequencer": "npm:30.2.0" + "@jest/types": "npm:30.2.0" + babel-jest: "npm:30.2.0" + chalk: "npm:^4.1.2" + ci-info: "npm:^4.2.0" + deepmerge: "npm:^4.3.1" + glob: "npm:^10.3.10" + graceful-fs: "npm:^4.2.11" + jest-circus: "npm:30.2.0" + jest-docblock: "npm:30.2.0" + jest-environment-node: "npm:30.2.0" + jest-regex-util: "npm:30.0.1" + jest-resolve: "npm:30.2.0" + jest-runner: "npm:30.2.0" + jest-util: "npm:30.2.0" + jest-validate: "npm:30.2.0" + micromatch: "npm:^4.0.8" parse-json: "npm:^5.2.0" - pretty-format: "npm:^29.7.0" + pretty-format: "npm:30.2.0" slash: "npm:^3.0.0" strip-json-comments: "npm:^3.1.1" peerDependencies: "@types/node": "*" + esbuild-register: ">=3.4.0" ts-node: ">=9.0.0" peerDependenciesMeta: "@types/node": optional: true + esbuild-register: + optional: true ts-node: optional: true - checksum: 10/6bdf570e9592e7d7dd5124fc0e21f5fe92bd15033513632431b211797e3ab57eaa312f83cc6481b3094b72324e369e876f163579d60016677c117ec4853cf02b + checksum: 10/296786b0a3d62de77e2f691f208d54ab541c1a73f87747d922eda643c6f25b89125ef3150170c07a6c8a316a30c15428e46237d499f688b0777f38de8a61ad16 languageName: node linkType: hard @@ -34965,7 +35202,19 @@ __metadata: languageName: node linkType: hard -"jest-diff@npm:^29.2.0, jest-diff@npm:^29.7.0": +"jest-diff@npm:30.2.0": + version: 30.2.0 + resolution: "jest-diff@npm:30.2.0" + dependencies: + "@jest/diff-sequences": "npm:30.0.1" + "@jest/get-type": "npm:30.1.0" + chalk: "npm:^4.1.2" + pretty-format: "npm:30.2.0" + checksum: 10/1fb9e4fb7dff81814b4f69eaa7db28e184d62306a3a8ea2447d02ca53d2cfa771e83ede513f67ec5239dffacfaac32ff2b49866d211e4c7516f51c1fc06ede42 + languageName: node + linkType: hard + +"jest-diff@npm:^29.2.0": version: 29.7.0 resolution: "jest-diff@npm:29.7.0" dependencies: @@ -34977,60 +35226,40 @@ __metadata: languageName: node linkType: hard -"jest-docblock@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-docblock@npm:29.7.0" +"jest-docblock@npm:30.2.0": + version: 30.2.0 + resolution: "jest-docblock@npm:30.2.0" dependencies: - detect-newline: "npm:^3.0.0" - checksum: 10/8d48818055bc96c9e4ec2e217a5a375623c0d0bfae8d22c26e011074940c202aa2534a3362294c81d981046885c05d304376afba9f2874143025981148f3e96d + detect-newline: "npm:^3.1.0" + checksum: 10/e01a7d1193947ed0f9713c26bfc7852e51cb758cafec807e5665a0a8d582473a43778bee099f8aa5c70b2941963e5341f4b10bd86b036a4fa3bcec0f4c04e099 languageName: node linkType: hard -"jest-each@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-each@npm:29.7.0" +"jest-each@npm:30.2.0": + version: 30.2.0 + resolution: "jest-each@npm:30.2.0" dependencies: - "@jest/types": "npm:^29.6.3" - chalk: "npm:^4.0.0" - jest-get-type: "npm:^29.6.3" - jest-util: "npm:^29.7.0" - pretty-format: "npm:^29.7.0" - checksum: 10/bd1a077654bdaa013b590deb5f7e7ade68f2e3289180a8c8f53bc8a49f3b40740c0ec2d3a3c1aee906f682775be2bebbac37491d80b634d15276b0aa0f2e3fda + "@jest/get-type": "npm:30.1.0" + "@jest/types": "npm:30.2.0" + chalk: "npm:^4.1.2" + jest-util: "npm:30.2.0" + pretty-format: "npm:30.2.0" + checksum: 10/f95e7dc1cef4b6a77899325702a214834ae25d01276cc31279654dc7e04f63c1925a37848dd16a0d16508c0fd3d182145f43c10af93952b7a689df3aeac198e9 languageName: node linkType: hard -"jest-environment-jsdom@npm:^29.0.2": - version: 29.7.0 - resolution: "jest-environment-jsdom@npm:29.7.0" +"jest-environment-node@npm:30.2.0": + version: 30.2.0 + resolution: "jest-environment-node@npm:30.2.0" dependencies: - "@jest/environment": "npm:^29.7.0" - "@jest/fake-timers": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - "@types/jsdom": "npm:^20.0.0" + "@jest/environment": "npm:30.2.0" + "@jest/fake-timers": "npm:30.2.0" + "@jest/types": "npm:30.2.0" "@types/node": "npm:*" - jest-mock: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - jsdom: "npm:^20.0.0" - peerDependencies: - canvas: ^2.5.0 - peerDependenciesMeta: - canvas: - optional: true - checksum: 10/23bbfc9bca914baef4b654f7983175a4d49b0f515a5094ebcb8f819f28ec186f53c0ba06af1855eac04bab1457f4ea79dae05f70052cf899863e8096daa6e0f5 - languageName: node - linkType: hard - -"jest-environment-node@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-environment-node@npm:29.7.0" - dependencies: - "@jest/environment": "npm:^29.7.0" - "@jest/fake-timers": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - "@types/node": "npm:*" - jest-mock: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - checksum: 10/9cf7045adf2307cc93aed2f8488942e39388bff47ec1df149a997c6f714bfc66b2056768973770d3f8b1bf47396c19aa564877eb10ec978b952c6018ed1bd637 + jest-mock: "npm:30.2.0" + jest-util: "npm:30.2.0" + jest-validate: "npm:30.2.0" + checksum: 10/7918bfea7367bd3e12dbbc4ea5afb193b5c47e480a6d1382512f051e2f028458fc9f5ef2f6260737ad41a0b1894661790ff3aaf3cbb4148a33ce2ce7aec64847 languageName: node linkType: hard @@ -35041,232 +35270,223 @@ __metadata: languageName: node linkType: hard -"jest-haste-map@npm:29.7.0": - version: 29.7.0 - resolution: "jest-haste-map@npm:29.7.0" +"jest-haste-map@npm:30.2.0": + version: 30.2.0 + resolution: "jest-haste-map@npm:30.2.0" dependencies: - "@jest/types": "npm:^29.6.3" - "@types/graceful-fs": "npm:^4.1.3" + "@jest/types": "npm:30.2.0" "@types/node": "npm:*" - anymatch: "npm:^3.0.3" - fb-watchman: "npm:^2.0.0" - fsevents: "npm:^2.3.2" - graceful-fs: "npm:^4.2.9" - jest-regex-util: "npm:^29.6.3" - jest-util: "npm:^29.7.0" - jest-worker: "npm:^29.7.0" - micromatch: "npm:^4.0.4" + anymatch: "npm:^3.1.3" + fb-watchman: "npm:^2.0.2" + fsevents: "npm:^2.3.3" + graceful-fs: "npm:^4.2.11" + jest-regex-util: "npm:30.0.1" + jest-util: "npm:30.2.0" + jest-worker: "npm:30.2.0" + micromatch: "npm:^4.0.8" walker: "npm:^1.0.8" dependenciesMeta: fsevents: optional: true - checksum: 10/8531b42003581cb18a69a2774e68c456fb5a5c3280b1b9b77475af9e346b6a457250f9d756bfeeae2fe6cbc9ef28434c205edab9390ee970a919baddfa08bb85 + checksum: 10/a88be6b0b672144aa30fe2d72e630d639c8d8729ee2cef84d0f830eac2005ac021cd8354f8ed8ecd74223f6a8b281efb62f466f5c9e01ed17650e38761051f4c languageName: node linkType: hard -"jest-haste-map@patch:jest-haste-map@npm%3A29.7.0#./.yarn/patches/jest-haste-map-npm-29.7.0-e3be419eff.patch::locator=root%40workspace%3A.": - version: 29.7.0 - resolution: "jest-haste-map@patch:jest-haste-map@npm%3A29.7.0#./.yarn/patches/jest-haste-map-npm-29.7.0-e3be419eff.patch::version=29.7.0&hash=feed88&locator=root%40workspace%3A." +"jest-leak-detector@npm:30.2.0": + version: 30.2.0 + resolution: "jest-leak-detector@npm:30.2.0" dependencies: - "@jest/types": "npm:^29.6.3" - "@types/graceful-fs": "npm:^4.1.3" - "@types/node": "npm:*" - anymatch: "npm:^3.0.3" - fb-watchman: "npm:^2.0.0" - fsevents: "npm:^2.3.2" - graceful-fs: "npm:^4.2.9" - jest-regex-util: "npm:^29.6.3" - jest-util: "npm:^29.7.0" - jest-worker: "npm:^29.7.0" - micromatch: "npm:^4.0.4" - walker: "npm:^1.0.8" - dependenciesMeta: - fsevents: - optional: true - checksum: 10/e93fab6316f0cdb5e930491efe87e9957a83200a58c37acedbea84472fbf67472675e3235d249b04726aaf09a23a5eadc4a7890e85969e2a8c7bf6b402c1ff5e + "@jest/get-type": "npm:30.1.0" + pretty-format: "npm:30.2.0" + checksum: 10/c430d6ed7910b2174738fbdca4ea64cbfe805216414c0d143c1090148f1389fec99d0733c0a8ed0a86709c89b4a4085b4749ac3a2cbc7deaf3ca87457afd24fc languageName: node linkType: hard -"jest-leak-detector@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-leak-detector@npm:29.7.0" +"jest-matcher-utils@npm:30.2.0": + version: 30.2.0 + resolution: "jest-matcher-utils@npm:30.2.0" dependencies: - jest-get-type: "npm:^29.6.3" - pretty-format: "npm:^29.7.0" - checksum: 10/e3950e3ddd71e1d0c22924c51a300a1c2db6cf69ec1e51f95ccf424bcc070f78664813bef7aed4b16b96dfbdeea53fe358f8aeaaea84346ae15c3735758f1605 + "@jest/get-type": "npm:30.1.0" + chalk: "npm:^4.1.2" + jest-diff: "npm:30.2.0" + pretty-format: "npm:30.2.0" + checksum: 10/f3f1ecf68ca63c9d1d80a175637a8fc655edfd1ee83220f6e3f6bd464ecbe2f93148fdd440a5a5e5a2b0b2cc8ee84ddc3dcef58a6dbc66821c792f48d260c6d4 languageName: node linkType: hard -"jest-matcher-utils@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-matcher-utils@npm:29.7.0" +"jest-message-util@npm:30.2.0": + version: 30.2.0 + resolution: "jest-message-util@npm:30.2.0" dependencies: - chalk: "npm:^4.0.0" - jest-diff: "npm:^29.7.0" - jest-get-type: "npm:^29.6.3" - pretty-format: "npm:^29.7.0" - checksum: 10/981904a494299cf1e3baed352f8a3bd8b50a8c13a662c509b6a53c31461f94ea3bfeffa9d5efcfeb248e384e318c87de7e3baa6af0f79674e987482aa189af40 - languageName: node - linkType: hard - -"jest-message-util@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-message-util@npm:29.7.0" - dependencies: - "@babel/code-frame": "npm:^7.12.13" - "@jest/types": "npm:^29.6.3" - "@types/stack-utils": "npm:^2.0.0" - chalk: "npm:^4.0.0" - graceful-fs: "npm:^4.2.9" - micromatch: "npm:^4.0.4" - pretty-format: "npm:^29.7.0" + "@babel/code-frame": "npm:^7.27.1" + "@jest/types": "npm:30.2.0" + "@types/stack-utils": "npm:^2.0.3" + chalk: "npm:^4.1.2" + graceful-fs: "npm:^4.2.11" + micromatch: "npm:^4.0.8" + pretty-format: "npm:30.2.0" slash: "npm:^3.0.0" - stack-utils: "npm:^2.0.3" - checksum: 10/31d53c6ed22095d86bab9d14c0fa70c4a92c749ea6ceece82cf30c22c9c0e26407acdfbdb0231435dc85a98d6d65ca0d9cbcd25cd1abb377fe945e843fb770b9 + stack-utils: "npm:^2.0.6" + checksum: 10/e29ec76e8c8e4da5f5b25198be247535626ccf3a940e93fdd51fc6a6bcf70feaa2921baae3806182a090431d90b08c939eb13fb64249b171d2e9ae3a452a8fd2 languageName: node linkType: hard -"jest-mock@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-mock@npm:29.7.0" +"jest-mock@npm:30.2.0": + version: 30.2.0 + resolution: "jest-mock@npm:30.2.0" dependencies: - "@jest/types": "npm:^29.6.3" + "@jest/types": "npm:30.2.0" "@types/node": "npm:*" - jest-util: "npm:^29.7.0" - checksum: 10/ae51d1b4f898724be5e0e52b2268a68fcd876d9b20633c864a6dd6b1994cbc48d62402b0f40f3a1b669b30ebd648821f086c26c08ffde192ced951ff4670d51c + jest-util: "npm:30.2.0" + checksum: 10/cde9b56805f90bf811a9231873ee88a0fb83bf4bf50972ae76960725da65220fcb119688f2e90e1ef33fbfd662194858d7f43809d881f1c41bb55d94e62adeab languageName: node linkType: hard -"jest-pnp-resolver@npm:^1.2.2": - version: 1.2.2 - resolution: "jest-pnp-resolver@npm:1.2.2" +"jest-pnp-resolver@npm:^1.2.3": + version: 1.2.3 + resolution: "jest-pnp-resolver@npm:1.2.3" peerDependencies: jest-resolve: "*" peerDependenciesMeta: jest-resolve: optional: true - checksum: 10/bd85dcc0e76e0eb0c3d56382ec140f08d25ff4068cda9d0e360bb78fb176cb726d0beab82dc0e8694cafd09f55fee7622b8bcb240afa5fad301f4ed3eebb4f47 + checksum: 10/db1a8ab2cb97ca19c01b1cfa9a9c8c69a143fde833c14df1fab0766f411b1148ff0df878adea09007ac6a2085ec116ba9a996a6ad104b1e58c20adbf88eed9b2 languageName: node linkType: hard -"jest-regex-util@npm:^29.6.3": - version: 29.6.3 - resolution: "jest-regex-util@npm:29.6.3" - checksum: 10/0518beeb9bf1228261695e54f0feaad3606df26a19764bc19541e0fc6e2a3737191904607fb72f3f2ce85d9c16b28df79b7b1ec9443aa08c3ef0e9efda6f8f2a +"jest-regex-util@npm:30.0.1": + version: 30.0.1 + resolution: "jest-regex-util@npm:30.0.1" + checksum: 10/fa8dac80c3e94db20d5e1e51d1bdf101cf5ede8f4e0b8f395ba8b8ea81e71804ffd747452a6bb6413032865de98ac656ef8ae43eddd18d980b6442a2764ed562 languageName: node linkType: hard -"jest-resolve-dependencies@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-resolve-dependencies@npm:29.7.0" +"jest-resolve-dependencies@npm:30.2.0": + version: 30.2.0 + resolution: "jest-resolve-dependencies@npm:30.2.0" dependencies: - jest-regex-util: "npm:^29.6.3" - jest-snapshot: "npm:^29.7.0" - checksum: 10/1e206f94a660d81e977bcfb1baae6450cb4a81c92e06fad376cc5ea16b8e8c6ea78c383f39e95591a9eb7f925b6a1021086c38941aa7c1b8a6a813c2f6e93675 + jest-regex-util: "npm:30.0.1" + jest-snapshot: "npm:30.2.0" + checksum: 10/0ff1a574f8c07f2e54a4ac8ab17aea00dfe2982e99b03fbd44f4211a94b8e5a59fdc43a59f9d6c0578a10a7b56a0611ad5ab40e4893973ff3f40dd414433b194 languageName: node linkType: hard -"jest-resolve@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-resolve@npm:29.7.0" +"jest-resolve@npm:30.2.0": + version: 30.2.0 + resolution: "jest-resolve@npm:30.2.0" dependencies: - chalk: "npm:^4.0.0" - graceful-fs: "npm:^4.2.9" - jest-haste-map: "npm:^29.7.0" - jest-pnp-resolver: "npm:^1.2.2" - jest-util: "npm:^29.7.0" - jest-validate: "npm:^29.7.0" - resolve: "npm:^1.20.0" - resolve.exports: "npm:^2.0.0" + chalk: "npm:^4.1.2" + graceful-fs: "npm:^4.2.11" + jest-haste-map: "npm:30.2.0" + jest-pnp-resolver: "npm:^1.2.3" + jest-util: "npm:30.2.0" + jest-validate: "npm:30.2.0" slash: "npm:^3.0.0" - checksum: 10/faa466fd9bc69ea6c37a545a7c6e808e073c66f46ab7d3d8a6ef084f8708f201b85d5fe1799789578b8b47fa1de47b9ee47b414d1863bc117a49e032ba77b7c7 + unrs-resolver: "npm:^1.7.11" + checksum: 10/e1f03da6811a946f5d885ea739a973975d099cc760641f9e1f90ac9c6621408538ba1e909f789d45d6e8d2411b78fb09230f16f15669621aa407aed7511fdf01 languageName: node linkType: hard -"jest-runner@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-runner@npm:29.7.0" +"jest-runner@npm:30.2.0": + version: 30.2.0 + resolution: "jest-runner@npm:30.2.0" dependencies: - "@jest/console": "npm:^29.7.0" - "@jest/environment": "npm:^29.7.0" - "@jest/test-result": "npm:^29.7.0" - "@jest/transform": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" + "@jest/console": "npm:30.2.0" + "@jest/environment": "npm:30.2.0" + "@jest/test-result": "npm:30.2.0" + "@jest/transform": "npm:30.2.0" + "@jest/types": "npm:30.2.0" "@types/node": "npm:*" - chalk: "npm:^4.0.0" + chalk: "npm:^4.1.2" emittery: "npm:^0.13.1" - graceful-fs: "npm:^4.2.9" - jest-docblock: "npm:^29.7.0" - jest-environment-node: "npm:^29.7.0" - jest-haste-map: "npm:^29.7.0" - jest-leak-detector: "npm:^29.7.0" - jest-message-util: "npm:^29.7.0" - jest-resolve: "npm:^29.7.0" - jest-runtime: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - jest-watcher: "npm:^29.7.0" - jest-worker: "npm:^29.7.0" + exit-x: "npm:^0.2.2" + graceful-fs: "npm:^4.2.11" + jest-docblock: "npm:30.2.0" + jest-environment-node: "npm:30.2.0" + jest-haste-map: "npm:30.2.0" + jest-leak-detector: "npm:30.2.0" + jest-message-util: "npm:30.2.0" + jest-resolve: "npm:30.2.0" + jest-runtime: "npm:30.2.0" + jest-util: "npm:30.2.0" + jest-watcher: "npm:30.2.0" + jest-worker: "npm:30.2.0" p-limit: "npm:^3.1.0" source-map-support: "npm:0.5.13" - checksum: 10/9d8748a494bd90f5c82acea99be9e99f21358263ce6feae44d3f1b0cd90991b5df5d18d607e73c07be95861ee86d1cbab2a3fc6ca4b21805f07ac29d47c1da1e + checksum: 10/d3706aa70e64a7ef8b38360d34ea6c261ba4d0b42136d7fb603c4fa71c24fa81f22c39ed2e39ee0db2363a42827810291f3ceb6a299e5996b41d701ad9b24184 languageName: node linkType: hard -"jest-runtime@npm:^29.0.2, jest-runtime@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-runtime@npm:29.7.0" +"jest-runtime@npm:30.2.0": + version: 30.2.0 + resolution: "jest-runtime@npm:30.2.0" dependencies: - "@jest/environment": "npm:^29.7.0" - "@jest/fake-timers": "npm:^29.7.0" - "@jest/globals": "npm:^29.7.0" - "@jest/source-map": "npm:^29.6.3" - "@jest/test-result": "npm:^29.7.0" - "@jest/transform": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" + "@jest/environment": "npm:30.2.0" + "@jest/fake-timers": "npm:30.2.0" + "@jest/globals": "npm:30.2.0" + "@jest/source-map": "npm:30.0.1" + "@jest/test-result": "npm:30.2.0" + "@jest/transform": "npm:30.2.0" + "@jest/types": "npm:30.2.0" "@types/node": "npm:*" - chalk: "npm:^4.0.0" - cjs-module-lexer: "npm:^1.0.0" - collect-v8-coverage: "npm:^1.0.0" - glob: "npm:^7.1.3" - graceful-fs: "npm:^4.2.9" - jest-haste-map: "npm:^29.7.0" - jest-message-util: "npm:^29.7.0" - jest-mock: "npm:^29.7.0" - jest-regex-util: "npm:^29.6.3" - jest-resolve: "npm:^29.7.0" - jest-snapshot: "npm:^29.7.0" - jest-util: "npm:^29.7.0" + chalk: "npm:^4.1.2" + cjs-module-lexer: "npm:^2.1.0" + collect-v8-coverage: "npm:^1.0.2" + glob: "npm:^10.3.10" + graceful-fs: "npm:^4.2.11" + jest-haste-map: "npm:30.2.0" + jest-message-util: "npm:30.2.0" + jest-mock: "npm:30.2.0" + jest-regex-util: "npm:30.0.1" + jest-resolve: "npm:30.2.0" + jest-snapshot: "npm:30.2.0" + jest-util: "npm:30.2.0" slash: "npm:^3.0.0" strip-bom: "npm:^4.0.0" - checksum: 10/59eb58eb7e150e0834a2d0c0d94f2a0b963ae7182cfa6c63f2b49b9c6ef794e5193ef1634e01db41420c36a94cefc512cdd67a055cd3e6fa2f41eaf0f82f5a20 + checksum: 10/81a3a9951420863f001e74c510bf35b85ae983f636f43ee1ffa1618b5a8ddafb681bc2810f71814bc8c8373e9593c89576b2325daf3c765e50057e48d5941df3 languageName: node linkType: hard -"jest-snapshot@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-snapshot@npm:29.7.0" +"jest-snapshot@npm:30.2.0": + version: 30.2.0 + resolution: "jest-snapshot@npm:30.2.0" dependencies: - "@babel/core": "npm:^7.11.6" - "@babel/generator": "npm:^7.7.2" - "@babel/plugin-syntax-jsx": "npm:^7.7.2" - "@babel/plugin-syntax-typescript": "npm:^7.7.2" - "@babel/types": "npm:^7.3.3" - "@jest/expect-utils": "npm:^29.7.0" - "@jest/transform": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - babel-preset-current-node-syntax: "npm:^1.0.0" - chalk: "npm:^4.0.0" - expect: "npm:^29.7.0" - graceful-fs: "npm:^4.2.9" - jest-diff: "npm:^29.7.0" - jest-get-type: "npm:^29.6.3" - jest-matcher-utils: "npm:^29.7.0" - jest-message-util: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - natural-compare: "npm:^1.4.0" - pretty-format: "npm:^29.7.0" - semver: "npm:^7.5.3" - checksum: 10/cb19a3948256de5f922d52f251821f99657339969bf86843bd26cf3332eae94883e8260e3d2fba46129a27c3971c1aa522490e460e16c7fad516e82d10bbf9f8 + "@babel/core": "npm:^7.27.4" + "@babel/generator": "npm:^7.27.5" + "@babel/plugin-syntax-jsx": "npm:^7.27.1" + "@babel/plugin-syntax-typescript": "npm:^7.27.1" + "@babel/types": "npm:^7.27.3" + "@jest/expect-utils": "npm:30.2.0" + "@jest/get-type": "npm:30.1.0" + "@jest/snapshot-utils": "npm:30.2.0" + "@jest/transform": "npm:30.2.0" + "@jest/types": "npm:30.2.0" + babel-preset-current-node-syntax: "npm:^1.2.0" + chalk: "npm:^4.1.2" + expect: "npm:30.2.0" + graceful-fs: "npm:^4.2.11" + jest-diff: "npm:30.2.0" + jest-matcher-utils: "npm:30.2.0" + jest-message-util: "npm:30.2.0" + jest-util: "npm:30.2.0" + pretty-format: "npm:30.2.0" + semver: "npm:^7.7.2" + synckit: "npm:^0.11.8" + checksum: 10/119390b49f397ed622ba7c375fc15f97af67c4fc49a34cf829c86ee732be2b06ad3c7171c76bb842a0e84a234783f1a4c721909aa316fbe00c6abc7c5962dfbc + languageName: node + linkType: hard + +"jest-util@npm:30.2.0": + version: 30.2.0 + resolution: "jest-util@npm:30.2.0" + dependencies: + "@jest/types": "npm:30.2.0" + "@types/node": "npm:*" + chalk: "npm:^4.1.2" + ci-info: "npm:^4.2.0" + graceful-fs: "npm:^4.2.11" + picomatch: "npm:^4.0.2" + checksum: 10/cf2f2fb83417ea69f9992121561c95cf4e9aad7946819b771b8b52addf78811101b33b51d0a39fa0c305f2751dab262feed7699de052659ff03d51827c8862f5 languageName: node linkType: hard @@ -35284,33 +35504,33 @@ __metadata: languageName: node linkType: hard -"jest-validate@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-validate@npm:29.7.0" +"jest-validate@npm:30.2.0": + version: 30.2.0 + resolution: "jest-validate@npm:30.2.0" dependencies: - "@jest/types": "npm:^29.6.3" - camelcase: "npm:^6.2.0" - chalk: "npm:^4.0.0" - jest-get-type: "npm:^29.6.3" + "@jest/get-type": "npm:30.1.0" + "@jest/types": "npm:30.2.0" + camelcase: "npm:^6.3.0" + chalk: "npm:^4.1.2" leven: "npm:^3.1.0" - pretty-format: "npm:^29.7.0" - checksum: 10/8ee1163666d8eaa16d90a989edba2b4a3c8ab0ffaa95ad91b08ca42b015bfb70e164b247a5b17f9de32d096987cada63ed8491ab82761bfb9a28bc34b27ae161 + pretty-format: "npm:30.2.0" + checksum: 10/61e66c6df29a1e181f8de063678dd2096bb52cc8a8ead3c9a3f853d54eca458ad04c7fb81931d9274affb67d0504a91a2a520456a139a26665810c3bf039b677 languageName: node linkType: hard -"jest-watcher@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-watcher@npm:29.7.0" +"jest-watcher@npm:30.2.0": + version: 30.2.0 + resolution: "jest-watcher@npm:30.2.0" dependencies: - "@jest/test-result": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" + "@jest/test-result": "npm:30.2.0" + "@jest/types": "npm:30.2.0" "@types/node": "npm:*" - ansi-escapes: "npm:^4.2.1" - chalk: "npm:^4.0.0" + ansi-escapes: "npm:^4.3.2" + chalk: "npm:^4.1.2" emittery: "npm:^0.13.1" - jest-util: "npm:^29.7.0" - string-length: "npm:^4.0.1" - checksum: 10/4f616e0345676631a7034b1d94971aaa719f0cd4a6041be2aa299be437ea047afd4fe05c48873b7963f5687a2f6c7cbf51244be8b14e313b97bfe32b1e127e55 + jest-util: "npm:30.2.0" + string-length: "npm:^4.0.2" + checksum: 10/fa38d06dcc59dbbd6a9ff22dea499d3c81ed376d9993b82d01797a99bf466d48641a99b9f3670a4b5480ca31144c5e017b96b7059e4d7541358fb48cf517a2db languageName: node linkType: hard @@ -35333,6 +35553,19 @@ __metadata: languageName: node linkType: hard +"jest-worker@npm:30.2.0": + version: 30.2.0 + resolution: "jest-worker@npm:30.2.0" + dependencies: + "@types/node": "npm:*" + "@ungap/structured-clone": "npm:^1.3.0" + jest-util: "npm:30.2.0" + merge-stream: "npm:^2.0.0" + supports-color: "npm:^8.1.1" + checksum: 10/9354b0c71c80173f673da6bbc0ddaad26e4395b06532f7332e0c1e93e855b873b10139b040e01eda77f3dc5a0b67613e2bd7c56c4947ee771acfc3611de2ca29 + languageName: node + linkType: hard + "jest-worker@npm:^27.4.5": version: 27.5.1 resolution: "jest-worker@npm:27.5.1" @@ -35356,31 +35589,31 @@ __metadata: languageName: node linkType: hard -"jest@npm:^29.7.0": - version: 29.7.0 - resolution: "jest@npm:29.7.0" +"jest@npm:^30.2.0": + version: 30.2.0 + resolution: "jest@npm:30.2.0" dependencies: - "@jest/core": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - import-local: "npm:^3.0.2" - jest-cli: "npm:^29.7.0" + "@jest/core": "npm:30.2.0" + "@jest/types": "npm:30.2.0" + import-local: "npm:^3.2.0" + jest-cli: "npm:30.2.0" peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 peerDependenciesMeta: node-notifier: optional: true bin: - jest: bin/jest.js - checksum: 10/97023d78446098c586faaa467fbf2c6b07ff06e2c85a19e3926adb5b0effe9ac60c4913ae03e2719f9c01ae8ffd8d92f6b262cedb9555ceeb5d19263d8c6362a + jest: ./bin/jest.js + checksum: 10/61c9d100750e4354cd7305d1f3ba253ffde4deaf12cb4be4d42d54f2dd5986e383a39c4a8691dbdc3839c69094a52413ed36f1886540ac37b71914a990b810d0 languageName: node linkType: hard -"jiti@npm:^2.0.0, jiti@npm:^2.4.2": - version: 2.4.2 - resolution: "jiti@npm:2.4.2" +"jiti@npm:^2.0.0, jiti@npm:^2.6.0": + version: 2.6.1 + resolution: "jiti@npm:2.6.1" bin: jiti: lib/jiti-cli.mjs - checksum: 10/e2b07eb2e3fbb245e29ad288dddecab31804967fc84d5e01d39858997d2743b5e248946defcecf99272275a00284ecaf7ec88b8c841331324f0c946d8274414b + checksum: 10/8cd72c5fd03a0502564c3f46c49761090f6dadead21fa191b73535724f095ad86c2fa89ee6fe4bc3515337e8d406cc8fb2d37b73fa0c99a34584bac35cd4a4de languageName: node linkType: hard @@ -35406,16 +35639,16 @@ __metadata: linkType: hard "jose@npm:^5.0.0": - version: 5.8.0 - resolution: "jose@npm:5.8.0" - checksum: 10/d5a11135754aeb7e3dcfbfb33ae1087541a081002cedb533e4705a9d71237a2c22f21085a291a0847939257a7b2f2374b589d306be17c3330049aefb62719c5e + version: 5.10.0 + resolution: "jose@npm:5.10.0" + checksum: 10/03881d1dfb390dcf50926402edcfe233bf557b5a77321fcb1bdb53453bc1cdd26d2d0a9ab28c7445cbb826881f84fdf5074179700f10c2711ccb9880f51065d7 languageName: node linkType: hard -"jose@npm:^6.0.10": - version: 6.0.10 - resolution: "jose@npm:6.0.10" - checksum: 10/8ba0d1aca94bdf780247c737328114fab3e394e1531e881dfe3fdd1d4a293b08cf7c0242769e72c8c8eee15c95761b542cf10730c3dfbb108084762df7d25cec +"jose@npm:^6.0.10, jose@npm:^6.1.1": + version: 6.1.3 + resolution: "jose@npm:6.1.3" + checksum: 10/9626c51e8c3792b505e954f3094698c182208617b62dfb27269230f31e57560b083985ed8128b8a9753aa92daf18d3a2341cc826d149503f14569abe87d42389 languageName: node linkType: hard @@ -35461,14 +35694,14 @@ __metadata: languageName: node linkType: hard -"js-yaml@npm:=4.1.0": - version: 4.1.0 - resolution: "js-yaml@npm:4.1.0" +"js-yaml@npm:=4.1.1, js-yaml@npm:^4.0.0, js-yaml@npm:^4.1.0, js-yaml@npm:^4.1.1, 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 @@ -35484,29 +35717,6 @@ __metadata: languageName: node linkType: hard -"js-yaml@npm:^4.0.0, 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/a52d0519f0f4ef5b4adc1cde466cb54c50d56e2b4a983b9d5c9c0f2f99462047007a6274d7e95617a21d3c91fde3ee6115536ed70991cd645ba8521058b78f77 - languageName: node - linkType: hard - -"js-yaml@npm:~3.13.1": - version: 3.13.1 - resolution: "js-yaml@npm:3.13.1" - dependencies: - argparse: "npm:^1.0.7" - esprima: "npm:^4.0.0" - bin: - js-yaml: bin/js-yaml.js - checksum: 10/cec89175b065743875fce53e63adc8b89aded77e18d00e54ff80c57ab730f22ccfddaf2fe3e6adab1d6dff59a3d55dd9ae6fc711d46335b7e94c32d3583a5627 - languageName: node - linkType: hard - "jsbn@npm:1.1.0, jsbn@npm:^1.1.0": version: 1.1.0 resolution: "jsbn@npm:1.1.0" @@ -35569,46 +35779,6 @@ __metadata: languageName: node linkType: hard -"jsdom@npm:^20.0.0": - version: 20.0.0 - resolution: "jsdom@npm:20.0.0" - dependencies: - abab: "npm:^2.0.6" - acorn: "npm:^8.7.1" - acorn-globals: "npm:^6.0.0" - cssom: "npm:^0.5.0" - cssstyle: "npm:^2.3.0" - data-urls: "npm:^3.0.2" - decimal.js: "npm:^10.3.1" - domexception: "npm:^4.0.0" - escodegen: "npm:^2.0.0" - form-data: "npm:^4.0.0" - html-encoding-sniffer: "npm:^3.0.0" - http-proxy-agent: "npm:^5.0.0" - https-proxy-agent: "npm:^5.0.1" - is-potential-custom-element-name: "npm:^1.0.1" - nwsapi: "npm:^2.2.0" - parse5: "npm:^7.0.0" - saxes: "npm:^6.0.0" - symbol-tree: "npm:^3.2.4" - tough-cookie: "npm:^4.0.0" - w3c-hr-time: "npm:^1.0.2" - w3c-xmlserializer: "npm:^3.0.0" - webidl-conversions: "npm:^7.0.0" - whatwg-encoding: "npm:^2.0.0" - whatwg-mimetype: "npm:^3.0.0" - whatwg-url: "npm:^11.0.0" - ws: "npm:^8.8.0" - xml-name-validator: "npm:^4.0.0" - peerDependencies: - canvas: ^2.5.0 - peerDependenciesMeta: - canvas: - optional: true - checksum: 10/5492ef8f496183bbce738977a0fb43b57a269d9d79fe12ea03d33ce9821a5ef54cb1580b94a662bf2d5ba463790fb61a9e892a87eb95d02e72bdf4068280a737 - languageName: node - linkType: hard - "jsdom@npm:^25.0.1": version: 25.0.1 resolution: "jsdom@npm:25.0.1" @@ -35643,6 +35813,39 @@ __metadata: languageName: node linkType: hard +"jsdom@npm:^27.1.0": + version: 27.3.0 + resolution: "jsdom@npm:27.3.0" + dependencies: + "@acemir/cssom": "npm:^0.9.28" + "@asamuzakjp/dom-selector": "npm:^6.7.6" + cssstyle: "npm:^5.3.4" + data-urls: "npm:^6.0.0" + decimal.js: "npm:^10.6.0" + html-encoding-sniffer: "npm:^4.0.0" + http-proxy-agent: "npm:^7.0.2" + https-proxy-agent: "npm:^7.0.6" + is-potential-custom-element-name: "npm:^1.0.1" + parse5: "npm:^8.0.0" + saxes: "npm:^6.0.0" + symbol-tree: "npm:^3.2.4" + tough-cookie: "npm:^6.0.0" + w3c-xmlserializer: "npm:^5.0.0" + webidl-conversions: "npm:^8.0.0" + whatwg-encoding: "npm:^3.1.1" + whatwg-mimetype: "npm:^4.0.0" + whatwg-url: "npm:^15.1.0" + ws: "npm:^8.18.3" + xml-name-validator: "npm:^5.0.0" + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + checksum: 10/c650e954df04a80e7309984450ce764ae2a840810b9575b20204194dee3c5cff42e65526519cb58b946ffe66a058b7b763bad4814b3903a9c86a2c1651b8b74b + languageName: node + linkType: hard + "jsep@npm:^1.2.0, jsep@npm:^1.3.6, jsep@npm:^1.4.0": version: 1.4.0 resolution: "jsep@npm:1.4.0" @@ -36088,45 +36291,45 @@ __metadata: languageName: node linkType: hard -"jwa@npm:^1.4.1": - version: 1.4.1 - resolution: "jwa@npm:1.4.1" +"jwa@npm:^1.4.2": + version: 1.4.2 + resolution: "jwa@npm:1.4.2" dependencies: - buffer-equal-constant-time: "npm:1.0.1" + buffer-equal-constant-time: "npm:^1.0.1" ecdsa-sig-formatter: "npm:1.0.11" safe-buffer: "npm:^5.0.1" - checksum: 10/0bc002b71dd70480fedc7d442a4d2b9185a9947352a027dcb4935864ad2323c57b5d391adf968a3622b61e940cef4f3484d5813b95864539272d41cac145d6f3 + checksum: 10/a46c9ddbcc226d9e85e13ef96328c7d331abddd66b5a55ec44bcf4350464a6125385ac9c1e64faa0fae8d586d90a14d6b5e96c73f0388970a3918d5252efb0f3 languageName: node linkType: hard -"jwa@npm:^2.0.0": - version: 2.0.0 - resolution: "jwa@npm:2.0.0" +"jwa@npm:^2.0.1": + version: 2.0.1 + resolution: "jwa@npm:2.0.1" dependencies: - buffer-equal-constant-time: "npm:1.0.1" + buffer-equal-constant-time: "npm:^1.0.1" ecdsa-sig-formatter: "npm:1.0.11" safe-buffer: "npm:^5.0.1" - checksum: 10/ab983f6685d99d13ddfbffef9b1c66309a536362a8412d49ba6e687d834a1240ce39290f30ac7dbe241e0ab6c76fee7ff795776ce534e11d148158c9b7193498 + checksum: 10/b04312a1de85f912b96aa3a7211717b8336945fab5b4f7cbc7800f4c80934060c0a3111576fad8d76e41ad62887d6da4b21fd4c47e45c174197f8be7dc0c1694 languageName: node linkType: hard "jws@npm:^3.2.2": - version: 3.2.2 - resolution: "jws@npm:3.2.2" + version: 3.2.3 + resolution: "jws@npm:3.2.3" dependencies: - jwa: "npm:^1.4.1" + jwa: "npm:^1.4.2" safe-buffer: "npm:^5.0.1" - checksum: 10/70b016974af8a76d25030c80a0097b24ed5b17a9cf10f43b163c11cb4eb248d5d04a3fe48c0d724d2884c32879d878ccad7be0663720f46b464f662f7ed778fe + checksum: 10/707387dd1cabcc3d9c2818f773cfaac7ede66e79ca11bbd159285a88cf5d8e8f355afcb8ee373e7bb0fcf9b7a2df015b22c50f27842f2c77453f04cd9f8f4009 languageName: node linkType: hard "jws@npm:^4.0.0": - version: 4.0.0 - resolution: "jws@npm:4.0.0" + version: 4.0.1 + resolution: "jws@npm:4.0.1" dependencies: - jwa: "npm:^2.0.0" + jwa: "npm:^2.0.1" safe-buffer: "npm:^5.0.1" - checksum: 10/1d15f4cdea376c6bd6a81002bd2cb0bf3d51d83da8f0727947b5ba3e10cf366721b8c0d099bf8c1eb99eb036e2c55e5fd5efd378ccff75a2b4e0bd10002348b9 + checksum: 10/75d7b157489fa9a72023712c58a7a7706c7e2b10eec27fabd3bb9cae0c9e492251ab72527d20a8a5f5726196f0508c320c643fddff7076657f6bca16d0ceeeeb languageName: node linkType: hard @@ -36154,11 +36357,11 @@ __metadata: linkType: hard "keyv@npm:*, keyv@npm:^5.2.1": - version: 5.3.4 - resolution: "keyv@npm:5.3.4" + version: 5.5.5 + resolution: "keyv@npm:5.5.5" dependencies: - "@keyv/serialize": "npm:^1.0.3" - checksum: 10/3e294eb1168af78ad3430d0cc47b6839fd3e70593238d35226a0a1e4094abe397ea378b7bce35cfcb314e55b5d1b4fcdae3c19bee5610e78d283b3cb5b279c8b + "@keyv/serialize": "npm:^1.1.1" + checksum: 10/4bbc2119151d67bfc04d3a08fc98f4efac5e171e8c99519fc7bc9ae9e05df4d1c6c835a5d133d563ed109a20db4688f3f9738400cbd3674f6303cdfc4490d429 languageName: node linkType: hard @@ -36251,32 +36454,28 @@ __metadata: linkType: hard "knip@npm:^5.42.0": - version: 5.42.2 - resolution: "knip@npm:5.42.2" + version: 5.71.0 + resolution: "knip@npm:5.71.0" dependencies: - "@nodelib/fs.walk": "npm:3.0.1" - "@snyk/github-codeowners": "npm:1.1.0" - easy-table: "npm:1.2.0" - enhanced-resolve: "npm:^5.18.0" + "@nodelib/fs.walk": "npm:^1.2.3" fast-glob: "npm:^3.3.3" - jiti: "npm:^2.4.2" - js-yaml: "npm:^4.1.0" + formatly: "npm:^0.3.0" + jiti: "npm:^2.6.0" + js-yaml: "npm:^4.1.1" minimist: "npm:^1.2.8" - picocolors: "npm:^1.1.0" + oxc-resolver: "npm:^11.13.2" + picocolors: "npm:^1.1.1" picomatch: "npm:^4.0.1" - pretty-ms: "npm:^9.0.0" - smol-toml: "npm:^1.3.1" - strip-json-comments: "npm:5.0.1" - summary: "npm:2.1.0" - zod: "npm:^3.22.4" - zod-validation-error: "npm:^3.0.3" + smol-toml: "npm:^1.5.2" + strip-json-comments: "npm:5.0.3" + zod: "npm:^4.1.11" peerDependencies: "@types/node": ">=18" - typescript: ">=5.0.4" + typescript: ">=5.0.4 <7" bin: knip: bin/knip.js knip-bun: bin/knip-bun.js - checksum: 10/1e540ad66e8e5cd2dfceb0c333ca46446300f4e40a51599ca6cd8de705bf0f928332ca108de28306ae7b54cc8fc1f66667135193baf32d4ccd30560797825935 + checksum: 10/68c1508be9db70865559f5ebc106f8a6aced8e4799949655e92a59fbb3f530c042087e50565cfdd698b619f69f584ee214cc7b315f3241d483d09e4fd200c45b languageName: node linkType: hard @@ -36329,14 +36528,14 @@ __metadata: linkType: hard "kubernetes-models@npm:^4.1.0, kubernetes-models@npm:^4.3.1": - version: 4.4.2 - resolution: "kubernetes-models@npm:4.4.2" + version: 4.5.1 + resolution: "kubernetes-models@npm:4.5.1" dependencies: - "@kubernetes-models/apimachinery": "npm:^2.0.2" + "@kubernetes-models/apimachinery": "npm:^2.2.0" "@kubernetes-models/base": "npm:^5.0.1" "@kubernetes-models/validate": "npm:^4.0.0" "@swc/helpers": "npm:^0.5.8" - checksum: 10/67956c3be831d02a0de3742809f1f8c89382eb11a9d66e7161f314413a1ddfce963513aa4eeba451a07a814b2f2afa959984a891f6e504e23e12825285a70a4e + checksum: 10/65a7b2064dcfbc719a80da7fd24b44775f68423170eb7abef3d4946604acdb5c8a3b2c8fded1d7212e2725b10ccf13abc94adf3091dc8005e25cd5321577ae92 languageName: node linkType: hard @@ -36392,16 +36591,15 @@ __metadata: linkType: hard "ldapts@npm:^8.0.6": - version: 8.0.9 - resolution: "ldapts@npm:8.0.9" + version: 8.0.25 + resolution: "ldapts@npm:8.0.25" dependencies: "@types/asn1": "npm:>=0.2.4" asn1: "npm:0.2.6" - debug: "npm:4.4.1" + debug: "npm:4.4.3" strict-event-emitter-types: "npm:2.0.0" - uuid: "npm:11.1.0" - whatwg-url: "npm:14.2.0" - checksum: 10/512bfec21e2c5d8a257ffda0c426221023043718b235dcc21d5ea81ba43d1999a2f14c1e9d6f7db7af1af2c467b46ee7016cac8da74ebae900fe208c54435b0e + whatwg-url: "npm:15.1.0" + checksum: 10/75fb390d5b4154b2d8c5da11e9d8123b1ae1bf50842a62a99309b90e96529939e2816c36f250dc6fb8e6f248e5da8012fe61522c058fe767f2429645649e51f5 languageName: node linkType: hard @@ -36709,10 +36907,10 @@ __metadata: languageName: node linkType: hard -"loader-runner@npm:^4.2.0": - version: 4.2.0 - resolution: "loader-runner@npm:4.2.0" - checksum: 10/89a648e0418f23edf2f310bf74a8adb0710548e8d8d47040def081e1b822bdc27b664b796ce43ceb7921fa56485e1f5046417e425714730dc6ea4242e7a176fa +"loader-runner@npm:^4.3.1": + version: 4.3.1 + resolution: "loader-runner@npm:4.3.1" + checksum: 10/d77127497c3f91fdba351e3e91156034e6e590e9f050b40df6c38ac16c54b5c903f7e2e141e09fefd046ee96b26fb50773c695ebc0aa205a4918683b124b04ba languageName: node linkType: hard @@ -36782,15 +36980,6 @@ __metadata: languageName: node linkType: hard -"locate-path@npm:^7.2.0": - version: 7.2.0 - resolution: "locate-path@npm:7.2.0" - dependencies: - p-locate: "npm:^6.0.0" - checksum: 10/1c6d269d4efec555937081be964e8a9b4a136319c79ca1d45ac6382212a8466113c75bd89e44521ca8ecd1c47fb08523b56eee5c0712bc7d14fec5f729deeb42 - languageName: node - linkType: hard - "lodash-es@npm:^4.17.21": version: 4.17.21 resolution: "lodash-es@npm:4.17.21" @@ -36917,20 +37106,6 @@ __metadata: languageName: node linkType: hard -"lodash.hasin@npm:^4.5.2": - version: 4.5.2 - resolution: "lodash.hasin@npm:4.5.2" - checksum: 10/d491be2eb53bcd004ac7eeefdc072fe6b76c701088bf95e8ca406a0b00e45739507ca6ffbce3829bdc9bc13e034877a2bbc08229e3f24d4095c3aa75f927d96a - languageName: node - linkType: hard - -"lodash.head@npm:^4.0.1": - version: 4.0.1 - resolution: "lodash.head@npm:4.0.1" - checksum: 10/37666c4f96ad907025754a0e76db49adab148bd2d1a1f4597c4bf7a006d4daad802453e162dffe08d2a488c1283a29f5366c1fc3a1bd2300423c32b0ecb26156 - languageName: node - linkType: hard - "lodash.isarguments@npm:^3.1.0": version: 3.1.0 resolution: "lodash.isarguments@npm:3.1.0" @@ -36959,20 +37134,6 @@ __metadata: languageName: node linkType: hard -"lodash.isnil@npm:^4.0.0": - version: 4.0.0 - resolution: "lodash.isnil@npm:4.0.0" - checksum: 10/ebf8df69879badd6ad99c4f64c54c470248df5cf92b208ca730861b1d8ac058da7b632ac811d18b0929d93cbac8d8fc866e781ee816b0142c56952e85edc682f - languageName: node - linkType: hard - -"lodash.isobject@npm:^3.0.2": - version: 3.0.2 - resolution: "lodash.isobject@npm:3.0.2" - checksum: 10/6c1667cbc4494d0a13a3617a4b23278d6d02dac520311f2bbb43f16f2cf71d2e6eb9dec8057315b77459df4890c756a256a087d3f4baa44a79ab5d6c968b060e - languageName: node - linkType: hard - "lodash.isstring@npm:^4.0.1": version: 4.0.1 resolution: "lodash.isstring@npm:4.0.1" @@ -37182,9 +37343,9 @@ __metadata: languageName: node linkType: hard -"logform@npm:^2.3.2, logform@npm:^2.6.0, logform@npm:^2.6.1": - version: 2.6.1 - resolution: "logform@npm:2.6.1" +"logform@npm:^2.3.2, logform@npm:^2.7.0": + version: 2.7.0 + resolution: "logform@npm:2.7.0" dependencies: "@colors/colors": "npm:1.6.0" "@types/triple-beam": "npm:^1.3.2" @@ -37192,7 +37353,7 @@ __metadata: ms: "npm:^2.1.1" safe-stable-stringify: "npm:^2.3.1" triple-beam: "npm:^1.3.0" - checksum: 10/e67f414787fbfe1e6a997f4c84300c7e06bee3d0bd579778af667e24b36db3ea200ed195d41b61311ff738dab7faabc615a07b174b22fe69e0b2f39e985be64b + checksum: 10/4b861bfd67efe599ab41113ae3ffe92b1873bf86793fb442f58971852430d8f416f9904da69e5043071fb3725690e2499a13acbfe92a57ba7d21690004f9edc0 languageName: node linkType: hard @@ -37282,17 +37443,17 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^10.0.0, lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": +"lru-cache@npm:^10.0.0, lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0, lru-cache@npm:^10.4.3": version: 10.4.3 resolution: "lru-cache@npm:10.4.3" checksum: 10/e6e90267360476720fa8e83cc168aa2bf0311f3f2eea20a6ba78b90a885ae72071d9db132f40fda4129c803e7dcec3a6b6a6fbb44ca90b081630b810b5d6a41a languageName: node linkType: hard -"lru-cache@npm:^11.0.0": - version: 11.0.2 - resolution: "lru-cache@npm:11.0.2" - checksum: 10/25fcb66e9d91eaf17227c6abfe526a7bed5903de74f93bfde380eb8a13410c5e8d3f14fe447293f3f322a7493adf6f9f015c6f1df7a235ff24ec30f366e1c058 +"lru-cache@npm:^11.0.0, lru-cache@npm:^11.2.2, lru-cache@npm:^11.2.4": + version: 11.2.4 + resolution: "lru-cache@npm:11.2.4" + checksum: 10/3b2da74c0b6653767f8164c38c4c4f4d7f0cc10c62bfa512663d94a830191ae6a5af742a8d88a8b30d5f9974652d3adae53931f32069139ad24fa2a18a199aca languageName: node linkType: hard @@ -37351,7 +37512,14 @@ __metadata: languageName: node linkType: hard -"luxon@npm:^3.0.0, luxon@npm:^3.2.1, luxon@npm:^3.4.3, luxon@npm:^3.5.0, luxon@npm:~3.5.0": +"luxon@npm:^3.0.0, luxon@npm:^3.2.1, luxon@npm:^3.4.3, luxon@npm:^3.5.0": + version: 3.7.2 + resolution: "luxon@npm:3.7.2" + checksum: 10/b24cd205ed306ce7415991687897dcc4027921ae413c9116590bc33a95f93b86ce52cf74ba72b4f5c5ab1c10090517f54ac8edfb127c049e0bf55b90dc2260be + languageName: node + linkType: hard + +"luxon@npm:~3.5.0": version: 3.5.0 resolution: "luxon@npm:3.5.0" checksum: 10/48f86e6c1c96815139f8559456a3354a276ba79bcef0ae0d4f2172f7652f3ba2be2237b0e103b8ea0b79b47715354ac9fac04eb1db3485dcc72d5110491dd47f @@ -37403,12 +37571,12 @@ __metadata: languageName: node linkType: hard -"magic-string@npm:^0.30.0, magic-string@npm:^0.30.10, magic-string@npm:^0.30.17, magic-string@npm:^0.30.3": - version: 0.30.17 - resolution: "magic-string@npm:0.30.17" +"magic-string@npm:^0.30.0, magic-string@npm:^0.30.17, magic-string@npm:^0.30.21, magic-string@npm:^0.30.3": + version: 0.30.21 + resolution: "magic-string@npm:0.30.21" dependencies: - "@jridgewell/sourcemap-codec": "npm:^1.5.0" - checksum: 10/2f71af2b0afd78c2e9012a29b066d2c8ba45a9cd0c8070f7fd72de982fb1c403b4e3afdb1dae00691d56885ede66b772ef6bedf765e02e3a7066208fe2fec4aa + "@jridgewell/sourcemap-codec": "npm:^1.5.5" + checksum: 10/57d5691f41ed40d962d8bd300148114f53db67fadbff336207db10a99f2bdf4a1be9cac3a68ee85dba575912ee1d4402e4396408196ec2d3afd043b076156221 languageName: node linkType: hard @@ -37826,6 +37994,13 @@ __metadata: languageName: node linkType: hard +"mdn-data@npm:2.12.2": + version: 2.12.2 + resolution: "mdn-data@npm:2.12.2" + checksum: 10/854e41715a9358e69f9a530117cd6ca7e71d06176469de8d70b1e629753b6827f5bd730995c16ad3750f3c9bad92230f8e4e178de2b34926b05f5205d27d76af + languageName: node + linkType: hard + "mdurl@npm:^1.0.0, mdurl@npm:^1.0.1": version: 1.0.1 resolution: "mdurl@npm:1.0.1" @@ -37908,15 +38083,17 @@ __metadata: languageName: node linkType: hard -"memfs@npm:^4.28.0, memfs@npm:^4.6.0": - version: 4.36.0 - resolution: "memfs@npm:4.36.0" +"memfs@npm:^4.51.1, memfs@npm:^4.6.0": + version: 4.51.1 + resolution: "memfs@npm:4.51.1" dependencies: - "@jsonjoy.com/json-pack": "npm:^1.0.3" - "@jsonjoy.com/util": "npm:^1.3.0" - tree-dump: "npm:^1.0.1" + "@jsonjoy.com/json-pack": "npm:^1.11.0" + "@jsonjoy.com/util": "npm:^1.9.0" + glob-to-regex.js: "npm:^1.0.1" + thingies: "npm:^2.5.0" + tree-dump: "npm:^1.0.3" tslib: "npm:^2.0.0" - checksum: 10/4898187c3278bd127340ec3d6c51d1fa77faa50aad71a8a1e4b7c02abbccd8485dccc7f565291b386b0cc96e5ac06902e4645108fad44e458fe8c1dfee3caf42 + checksum: 10/a2f70cd1b366f910a39bb1a398c03d6f3f0db936376697eca3e176609e9f6acc0ed40fb44d6293dd15eb3f730c3fce536e5024395e3dc92f54374133c96ba1b7 languageName: node linkType: hard @@ -38005,8 +38182,8 @@ __metadata: linkType: hard "micromark-core-commonmark@npm:^1.0.0, micromark-core-commonmark@npm:^1.0.1": - version: 1.0.6 - resolution: "micromark-core-commonmark@npm:1.0.6" + version: 1.1.0 + resolution: "micromark-core-commonmark@npm:1.1.0" dependencies: decode-named-character-reference: "npm:^1.0.0" micromark-factory-destination: "npm:^1.0.0" @@ -38024,25 +38201,25 @@ __metadata: micromark-util-symbol: "npm:^1.0.0" micromark-util-types: "npm:^1.0.1" uvu: "npm:^0.5.0" - checksum: 10/20daa4b78b88afea7658c2bd428c830734c72fbb2184c1f0761bb4c1e5fcf266509e7d46ad5f7b2a2aeb32cd17951788733cad458632457b52397534d930030a + checksum: 10/a73694d223ac8baad8ff00597a3c39d61f5b32bfd56fe4bcf295d75b2a4e8e67fb2edbfc7cc287b362b9d7f6d24fce08b6a7e8b5b155d79bcc1e4d9b2756ffb2 languageName: node linkType: hard "micromark-extension-gfm-autolink-literal@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-extension-gfm-autolink-literal@npm:1.0.0" + version: 1.0.5 + resolution: "micromark-extension-gfm-autolink-literal@npm:1.0.5" dependencies: micromark-util-character: "npm:^1.0.0" micromark-util-sanitize-uri: "npm:^1.0.0" micromark-util-symbol: "npm:^1.0.0" micromark-util-types: "npm:^1.0.0" - checksum: 10/4bb5841980725bbbe59b311b5b5efbab74b363da00669b96e7744ea76532d972720d1ce92a024baeafbac2c92dbf90b5595a18212415a59ced7610202304ce13 + checksum: 10/1e0ccc758baef3cd0478ba84ff86fa1ec2b389042421c7cade9485b775456c1a9c3bd797393002b2c6f6abd9bdf829cb114874557bbcb8e43d16d06a464811c0 languageName: node linkType: hard "micromark-extension-gfm-footnote@npm:^1.0.0": - version: 1.0.3 - resolution: "micromark-extension-gfm-footnote@npm:1.0.3" + version: 1.1.2 + resolution: "micromark-extension-gfm-footnote@npm:1.1.2" dependencies: micromark-core-commonmark: "npm:^1.0.0" micromark-factory-space: "npm:^1.0.0" @@ -38050,60 +38227,64 @@ __metadata: micromark-util-normalize-identifier: "npm:^1.0.0" micromark-util-sanitize-uri: "npm:^1.0.0" micromark-util-symbol: "npm:^1.0.0" + micromark-util-types: "npm:^1.0.0" uvu: "npm:^0.5.0" - checksum: 10/1e8920582a2d365eebe48239daaeade3b88d7f6b800f1dd7e7b0d6add730105048d7d1fd68138955b397d572d410c280345560e407328c07fa334e17a965a578 + checksum: 10/8777073fb76d2fd01f6b2405106af6c349c1e25660c4d37cadcc61c187d71c8444870f73cefaaa67f12884d5e45c78ee3c5583561a0b330bd91c6d997113584a languageName: node linkType: hard "micromark-extension-gfm-strikethrough@npm:^1.0.0": - version: 1.0.1 - resolution: "micromark-extension-gfm-strikethrough@npm:1.0.1" + version: 1.0.7 + resolution: "micromark-extension-gfm-strikethrough@npm:1.0.7" dependencies: micromark-util-chunked: "npm:^1.0.0" micromark-util-classify-character: "npm:^1.0.0" micromark-util-resolve-all: "npm:^1.0.0" micromark-util-symbol: "npm:^1.0.0" micromark-util-types: "npm:^1.0.0" - checksum: 10/720aa4e47ac4701faf5640be40a97a249b880d5f8f2787106a0d12bd11dd8ab9e29a513af738743be07d42a02734e024184114b719435160e012c4ba9f7464bd + uvu: "npm:^0.5.0" + checksum: 10/8411ef1aa5dc83f662e8b45b085f70ddff29deb3c4259269e8a1ff656397abb755d8ea841a14be23e8585a31d3c0a5de1bd2c05f3453b66670e499d4a0004f5e languageName: node linkType: hard "micromark-extension-gfm-table@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-extension-gfm-table@npm:1.0.0" + version: 1.0.7 + resolution: "micromark-extension-gfm-table@npm:1.0.7" dependencies: micromark-factory-space: "npm:^1.0.0" micromark-util-character: "npm:^1.0.0" micromark-util-symbol: "npm:^1.0.0" micromark-util-types: "npm:^1.0.0" - checksum: 10/220e2d5c66e4dcd8565cc067d584136fc91dbf249bcee69c6a6cb5656b536144d29734d922602bdfd82242dad2a3d128b25f57e93c50091fd96d7071618eedd9 + uvu: "npm:^0.5.0" + checksum: 10/f05d86a099c941a2a309d60bf4839d16a00a93cb880cda4ab8faeb831647763fff6e03197ec15b80e1f195002afcca6afe2b95c3622b049b82d7ff8ef1c1c776 languageName: node linkType: hard "micromark-extension-gfm-tagfilter@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-extension-gfm-tagfilter@npm:1.0.0" + version: 1.0.2 + resolution: "micromark-extension-gfm-tagfilter@npm:1.0.2" dependencies: micromark-util-types: "npm:^1.0.0" - checksum: 10/2950168c4329486da0d411afebae47601dbbdb8ba51892d88ef451dd62e4d412afaba267f1e4a1d0c93b1b25723cfcdcbb013c458f652e8ab2a1046dd1b5c352 + checksum: 10/55c7d9019d6a39efaaed2c2e40b0aaa137d2c4f9c94cac82e93f509a806c3a775e4c815b5d8e986617450b68861a19776e4b886307e83db452b393f15a837b39 languageName: node linkType: hard "micromark-extension-gfm-task-list-item@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-extension-gfm-task-list-item@npm:1.0.0" + version: 1.0.5 + resolution: "micromark-extension-gfm-task-list-item@npm:1.0.5" dependencies: micromark-factory-space: "npm:^1.0.0" micromark-util-character: "npm:^1.0.0" micromark-util-symbol: "npm:^1.0.0" micromark-util-types: "npm:^1.0.0" - checksum: 10/a8bc41ce4c5599cde45804737381ca5136b43c64c05034e396e476958232916d3bc23e585d3f71c59f03666c568bced41f177a76497267259b813ab4987dd3a5 + uvu: "npm:^0.5.0" + checksum: 10/46bb1baa10bfb785a2e3e2f975e5509260b9995d5c3aeddf77051957d218ce1af4ea737bcb6a56a930e62d42b05307b20632a400eff25cdb290789ff3170cad5 languageName: node linkType: hard "micromark-extension-gfm@npm:^2.0.0": - version: 2.0.1 - resolution: "micromark-extension-gfm@npm:2.0.1" + version: 2.0.3 + resolution: "micromark-extension-gfm@npm:2.0.3" dependencies: micromark-extension-gfm-autolink-literal: "npm:^1.0.0" micromark-extension-gfm-footnote: "npm:^1.0.0" @@ -38113,200 +38294,204 @@ __metadata: micromark-extension-gfm-task-list-item: "npm:^1.0.0" micromark-util-combine-extensions: "npm:^1.0.0" micromark-util-types: "npm:^1.0.0" - checksum: 10/701d065102685a338c7eb87520587575dffbc240639a379c64f83971b3ffbeaf0165aa43ce72b0255856e991b395a5fc71dc436ec76d723f462a39ec16eade5e + checksum: 10/3ffd06ced4314abd0f0c72ec227f034f38dd47facbb62439ef3216d42f32433f3901d14675cf806e8d73689802a11849958b330bb5b55dd4fd5cdc64ebaf345c languageName: node linkType: hard "micromark-factory-destination@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-factory-destination@npm:1.0.0" + version: 1.1.0 + resolution: "micromark-factory-destination@npm:1.1.0" dependencies: micromark-util-character: "npm:^1.0.0" micromark-util-symbol: "npm:^1.0.0" micromark-util-types: "npm:^1.0.0" - checksum: 10/8e733ae9c1c2342f14ff290bf09946e20f6f540117d80342377a765cac48df2ea5e748f33c8b07501ad7a43414b1a6597c8510ede2052b6bf1251fab89748e20 + checksum: 10/9e2b5fb5fedbf622b687e20d51eb3d56ae90c0e7ecc19b37bd5285ec392c1e56f6e21aa7cfcb3c01eda88df88fe528f3acb91a5f57d7f4cba310bc3cd7f824fa languageName: node linkType: hard "micromark-factory-label@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-factory-label@npm:1.0.0" + version: 1.1.0 + resolution: "micromark-factory-label@npm:1.1.0" dependencies: micromark-util-character: "npm:^1.0.0" micromark-util-symbol: "npm:^1.0.0" micromark-util-types: "npm:^1.0.0" - checksum: 10/37f34d8e42e3a606a636419ffe0f99fd7f6054778365aac56d1cc11585d1cd46000dbf938319abb2ea5e245803679d3cfa46afb5aecf2702bfd963113481f14c + uvu: "npm:^0.5.0" + checksum: 10/fcda48f1287d9b148c562c627418a2ab759cdeae9c8e017910a0cba94bb759a96611e1fc6df33182e97d28fbf191475237298983bb89ef07d5b02464b1ad28d5 languageName: node linkType: hard "micromark-factory-space@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-factory-space@npm:1.0.0" + version: 1.1.0 + resolution: "micromark-factory-space@npm:1.1.0" dependencies: micromark-util-character: "npm:^1.0.0" micromark-util-types: "npm:^1.0.0" - checksum: 10/70d3aafde4e68ef4e509a3b644e9a29e4aada00801279e346577b008cbca06d78051bcd62aa7ea7425856ed73f09abd2b36607803055f726f52607ee7cb706b0 + checksum: 10/b58435076b998a7e244259a4694eb83c78915581206b6e7fc07b34c6abd36a1726ade63df8972fbf6c8fa38eecb9074f4e17be8d53f942e3b3d23d1a0ecaa941 languageName: node linkType: hard "micromark-factory-title@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-factory-title@npm:1.0.0" + version: 1.1.0 + resolution: "micromark-factory-title@npm:1.1.0" dependencies: micromark-factory-space: "npm:^1.0.0" micromark-util-character: "npm:^1.0.0" micromark-util-symbol: "npm:^1.0.0" micromark-util-types: "npm:^1.0.0" - checksum: 10/d9bf0779c49f013fea5fc5daadcee91e1703c81c1b5091337e806c24f1ea224d69f534d174aba2eccff7826620af71bb3db83b1caf14cbb89bea53f4e8e38bc3 + checksum: 10/4432d3dbc828c81f483c5901b0c6591a85d65a9e33f7d96ba7c3ae821617a0b3237ff5faf53a9152d00aaf9afb3a9f185b205590f40ed754f1d9232e0e9157b1 languageName: node linkType: hard "micromark-factory-whitespace@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-factory-whitespace@npm:1.0.0" + version: 1.1.0 + resolution: "micromark-factory-whitespace@npm:1.1.0" dependencies: micromark-factory-space: "npm:^1.0.0" micromark-util-character: "npm:^1.0.0" micromark-util-symbol: "npm:^1.0.0" micromark-util-types: "npm:^1.0.0" - checksum: 10/0888386e6ea2dd665a5182c570d9b3d0a172d3f11694ca5a2a84e552149c9f1429f5b975ec26e1f0fa4388c55a656c9f359ce5e0603aff6175ba3e255076f20b + checksum: 10/ef0fa682c7d593d85a514ee329809dee27d10bc2a2b65217d8ef81173e33b8e83c549049764b1ad851adfe0a204dec5450d9d20a4ca8598f6c94533a73f73fcd languageName: node linkType: hard "micromark-util-character@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-util-character@npm:1.1.0" + version: 1.2.0 + resolution: "micromark-util-character@npm:1.2.0" dependencies: micromark-util-symbol: "npm:^1.0.0" micromark-util-types: "npm:^1.0.0" - checksum: 10/81a1e4ee996e89966f58620088ca1ad49a6b1474fa488992be9b6f62d783d621c33f74c01f8560a2960412a43e83c7d991c711620ff3ee49169eb77de0bb2e3a + checksum: 10/88cf80f9b4c95266f24814ef587fb4180454668dcc3be4ac829e1227188cf349c8981bfca29e3eab1682f324c2c47544c0b0b799a26fbf9df5f156c6a84c970c languageName: node linkType: hard "micromark-util-chunked@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-util-chunked@npm:1.0.0" + version: 1.1.0 + resolution: "micromark-util-chunked@npm:1.1.0" dependencies: micromark-util-symbol: "npm:^1.0.0" - checksum: 10/c1efd56e8c4217bcf1c6f1a9fb9912b4a2a5503b00d031da902be922fb3fee60409ac53f11739991291357b2784fb0647ddfc74c94753a068646c0cb0fd71421 + checksum: 10/c435bde9110cb595e3c61b7f54c2dc28ee03e6a57fa0fc1e67e498ad8bac61ee5a7457a2b6a73022ddc585676ede4b912d28dcf57eb3bd6951e54015e14dc20b languageName: node linkType: hard "micromark-util-classify-character@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-util-classify-character@npm:1.0.0" + version: 1.1.0 + resolution: "micromark-util-classify-character@npm:1.1.0" dependencies: micromark-util-character: "npm:^1.0.0" micromark-util-symbol: "npm:^1.0.0" micromark-util-types: "npm:^1.0.0" - checksum: 10/180446e6a1dec653f625ded028f244784e1db8d10ad05c5d70f08af9de393b4a03dc6cf6fa5ed8ccc9c24bbece7837abf3bf66681c0b4adf159364b7d5236dfd + checksum: 10/8499cb0bb1f7fb946f5896285fcca65cd742f66cd3e79ba7744792bd413ec46834f932a286de650349914d02e822946df3b55d03e6a8e1d245d1ddbd5102e5b0 languageName: node linkType: hard "micromark-util-combine-extensions@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-util-combine-extensions@npm:1.0.0" + version: 1.1.0 + resolution: "micromark-util-combine-extensions@npm:1.1.0" dependencies: micromark-util-chunked: "npm:^1.0.0" micromark-util-types: "npm:^1.0.0" - checksum: 10/5304a820ef75340e1be69d6ad167055b6ba9a3bafe8171e5945a935752f462415a9dd61eb3490220c055a8a11167209a45bfa73f278338b7d3d61fa1464d3f35 + checksum: 10/ee78464f5d4b61ccb437850cd2d7da4d690b260bca4ca7a79c4bb70291b84f83988159e373b167181b6716cb197e309bc6e6c96a68cc3ba9d50c13652774aba9 languageName: node linkType: hard "micromark-util-decode-numeric-character-reference@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-util-decode-numeric-character-reference@npm:1.0.0" + version: 1.1.0 + resolution: "micromark-util-decode-numeric-character-reference@npm:1.1.0" dependencies: micromark-util-symbol: "npm:^1.0.0" - checksum: 10/f3ae2bb582a80f1e9d3face026f585c0c472335c064bd850bde152376f0394cb2831746749b6be6e0160f7d73626f67d10716026c04c87f402c0dd45a1a28633 + checksum: 10/4733fe75146e37611243f055fc6847137b66f0cde74d080e33bd26d0408c1d6f44cabc984063eee5968b133cb46855e729d555b9ff8d744652262b7b51feec73 languageName: node linkType: hard "micromark-util-decode-string@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-util-decode-string@npm:1.0.0" + version: 1.1.0 + resolution: "micromark-util-decode-string@npm:1.1.0" dependencies: + decode-named-character-reference: "npm:^1.0.0" micromark-util-character: "npm:^1.0.0" micromark-util-decode-numeric-character-reference: "npm:^1.0.0" - parse-entities: "npm:^3.0.0" - checksum: 10/9c7e6e3950e675f39058935e065e8774a1ef26a0d386eb45abd96fb8c0312c8a72464c672f41a3c7668974ac2a7cb2ad23b84eee3775b0bd4c47947d9fc64888 + micromark-util-symbol: "npm:^1.0.0" + checksum: 10/f1625155db452f15aa472918499689ba086b9c49d1322a08b22bfbcabe918c61b230a3002c8bc3ea9b1f52ca7a9bb1c3dd43ccb548c7f5f8b16c24a1ae77a813 languageName: node linkType: hard "micromark-util-encode@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-util-encode@npm:1.0.0" - checksum: 10/16985a6b355721307553d1893da364e83144ef068f84978071a9b4b3d884b65c3138f8330fb039aac10f75766b4906e03c5e62baafb1bf5e731f959878277712 + version: 1.1.0 + resolution: "micromark-util-encode@npm:1.1.0" + checksum: 10/4ef29d02b12336918cea6782fa87c8c578c67463925221d4e42183a706bde07f4b8b5f9a5e1c7ce8c73bb5a98b261acd3238fecd152e6dd1cdfa2d1ae11b60a0 languageName: node linkType: hard "micromark-util-html-tag-name@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-util-html-tag-name@npm:1.0.0" - checksum: 10/ed07ce9b9bb30cc4ea57f733089b3a253a6132c0608ccfc105eadb32f1f80bbd2347bf8a74f897fe039d7805a59f602fd4dd15f6adc7926d40b3646da2888d0f + version: 1.2.0 + resolution: "micromark-util-html-tag-name@npm:1.2.0" + checksum: 10/ccf0fa99b5c58676dc5192c74665a3bfd1b536fafaf94723bd7f31f96979d589992df6fcf2862eba290ef18e6a8efb30ec8e1e910d9f3fc74f208871e9f84750 languageName: node linkType: hard "micromark-util-normalize-identifier@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-util-normalize-identifier@npm:1.0.0" + version: 1.1.0 + resolution: "micromark-util-normalize-identifier@npm:1.1.0" dependencies: micromark-util-symbol: "npm:^1.0.0" - checksum: 10/d7c09d5e8318fb72f194af72664bd84a48a2928e3550b2b21c8fbc0ec22524f2a72e0f6663d2b95dc189a6957d3d7759b60716e888909710767cd557be821f8b + checksum: 10/8655bea41ffa4333e03fc22462cb42d631bbef9c3c07b625fd852b7eb442a110f9d2e5902a42e65188d85498279569502bf92f3434a1180fc06f7c37edfbaee2 languageName: node linkType: hard "micromark-util-resolve-all@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-util-resolve-all@npm:1.0.0" + version: 1.1.0 + resolution: "micromark-util-resolve-all@npm:1.1.0" dependencies: micromark-util-types: "npm:^1.0.0" - checksum: 10/409667f2bd126ef8acce009270d2aecaaa5584c5807672bc657b09e50aa91bd2e552cf41e5be1e6469244a83349cbb71daf6059b746b1c44e3f35446fef63e50 + checksum: 10/1ce6c0237cd3ca061e76fae6602cf95014e764a91be1b9f10d36cb0f21ca88f9a07de8d49ab8101efd0b140a4fbfda6a1efb72027ab3f4d5b54c9543271dc52c languageName: node linkType: hard "micromark-util-sanitize-uri@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-util-sanitize-uri@npm:1.0.0" + version: 1.2.0 + resolution: "micromark-util-sanitize-uri@npm:1.2.0" dependencies: micromark-util-character: "npm:^1.0.0" micromark-util-encode: "npm:^1.0.0" micromark-util-symbol: "npm:^1.0.0" - checksum: 10/198e91b9f86d429ebc74737c0378764c8dc2e988f46a6846f59223938bc9f3a5d4ae97ce9ce801ac6f3a76cce3da1196cbb3d5ba13bb4cce53026b06891a61da + checksum: 10/0d024100d95ffb88bf75f3360e305b545c1eb745430959b8633f7aa93f37ec401fc7094c90c97298409a9e30d94d53b895bae224e1bb966bea114976cfa0fd48 languageName: node linkType: hard "micromark-util-subtokenize@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-util-subtokenize@npm:1.0.0" + version: 1.1.0 + resolution: "micromark-util-subtokenize@npm:1.1.0" dependencies: micromark-util-chunked: "npm:^1.0.0" micromark-util-symbol: "npm:^1.0.0" micromark-util-types: "npm:^1.0.0" - checksum: 10/0b19352a05c958c38782be7be6ca1c5ce004f24b4d5fdb70666d2dfa4c75d859a0fc44ef3e899ad085fe3022b0cd1201748f96b5617d09e43ea455ba2e2d0f9a + uvu: "npm:^0.5.0" + checksum: 10/075a1db6ea586d65827d3eead33dbfc520c4e43659c93fcd8fd82f44a7b75cfe61dcde967a3dfcc2ffd999347440ba5aa6698e65a04f3fc627e13e9f12a1a910 languageName: node linkType: hard "micromark-util-symbol@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-util-symbol@npm:1.0.0" - checksum: 10/93945fd4863bcf9e4186ee6f234a1ef790f884e927287aed6741d41d37dae4c0783d36f3f811e94e1634ea86c6bb484b6d2691d5e87a471aee32500cb1a4c136 + version: 1.1.0 + resolution: "micromark-util-symbol@npm:1.1.0" + checksum: 10/a26b6b1efd77a715a4d9bbe0a5338eaf3d04ea5e85733e34fee56dfeabf64495c0afc5438fe5220316884cd3a5eae1f17768e0ff4e117827ea4a653897466f86 languageName: node linkType: hard "micromark-util-types@npm:^1.0.0, micromark-util-types@npm:^1.0.1": - version: 1.0.1 - resolution: "micromark-util-types@npm:1.0.1" - checksum: 10/cc270381bb1035e610592d76a9889ec7d2d8f613bd6c2cbbfc09cae8232602af132ca4d2a5d498b0f9e30db6bc33e0320bb7386ee2abc7267d6a1c25dadcbd85 + version: 1.1.0 + resolution: "micromark-util-types@npm:1.1.0" + checksum: 10/287ac5de4a3802bb6f6c3842197c294997a488db1c0486e03c7a8e674d9eb7720c17dda1bcb814814b8343b338c4826fcbc0555f3e75463712a60dcdb53a028e languageName: node linkType: hard "micromark@npm:^3.0.0": - version: 3.0.5 - resolution: "micromark@npm:3.0.5" + version: 3.2.0 + resolution: "micromark@npm:3.2.0" dependencies: "@types/debug": "npm:^4.0.0" debug: "npm:^4.0.0" + decode-named-character-reference: "npm:^1.0.0" micromark-core-commonmark: "npm:^1.0.1" micromark-factory-space: "npm:^1.0.0" micromark-util-character: "npm:^1.0.0" @@ -38320,12 +38505,12 @@ __metadata: micromark-util-subtokenize: "npm:^1.0.0" micromark-util-symbol: "npm:^1.0.0" micromark-util-types: "npm:^1.0.1" - parse-entities: "npm:^3.0.0" - checksum: 10/cb6c259849562f69f901150f37343ed7f952b75d01a3f0c312b5a0fe58c5f3304067c66be5dcf4a781a4da51073a631c2db095d3647f5d6d7895b44f181197b5 + uvu: "npm:^0.5.0" + checksum: 10/560a4a501efc3859d622461aaa9345fb95b99a2f34d3d3f2a775ab04de1dd857cb0f642083a6b28ab01bd817f5f0741a1be9857fd702f45e04a3752927a66719 languageName: node linkType: hard -"micromatch@npm:^4.0.2, micromatch@npm:^4.0.4, micromatch@npm:^4.0.5, micromatch@npm:^4.0.7, micromatch@npm:^4.0.8": +"micromatch@npm:^4.0.2, micromatch@npm:^4.0.5, micromatch@npm:^4.0.7, micromatch@npm:^4.0.8": version: 4.0.8 resolution: "micromatch@npm:4.0.8" dependencies: @@ -38522,6 +38707,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:10.0.3": + version: 10.0.3 + resolution: "minimatch@npm:10.0.3" + dependencies: + "@isaacs/brace-expansion": "npm:^5.0.0" + checksum: 10/d5b8b2538b367f2cfd4aeef27539fddeee58d1efb692102b848e4a968a09780a302c530eb5aacfa8c57f7299155fb4b4e85219ad82664dcef5c66f657111d9b8 + languageName: node + linkType: hard + "minimatch@npm:3.1.2, minimatch@npm:^3.0.2, minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" @@ -38576,15 +38770,6 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:~3.0.3": - version: 3.0.8 - resolution: "minimatch@npm:3.0.8" - dependencies: - brace-expansion: "npm:^1.1.7" - checksum: 10/6df5373cb1ea79020beb6887ff5576c58cfabcfd32c5a65c2cf58f326e4ee8eae84f129e5fa50b8a4347fa1d1e583f931285c9fb3040d984bdfb5109ef6607ec - languageName: node - linkType: hard - "minimist@npm:^1.1.0, minimist@npm:^1.2.0, minimist@npm:^1.2.3, minimist@npm:^1.2.5, minimist@npm:^1.2.6, minimist@npm:^1.2.8": version: 1.2.8 resolution: "minimist@npm:1.2.8" @@ -38870,8 +39055,8 @@ __metadata: linkType: hard "mockttp@npm:^3.13.0": - version: 3.15.5 - resolution: "mockttp@npm:3.15.5" + version: 3.17.1 + resolution: "mockttp@npm:3.17.1" dependencies: "@graphql-tools/schema": "npm:^8.5.0" "@graphql-tools/utils": "npm:^8.8.0" @@ -38889,7 +39074,7 @@ __metadata: cors: "npm:^2.8.4" cors-gate: "npm:^1.1.3" cross-fetch: "npm:^3.1.5" - destroyable-server: "npm:^1.0.2" + destroyable-server: "npm:^1.1.1" express: "npm:^4.14.0" fast-json-patch: "npm:^3.1.1" graphql: "npm:^14.0.2 || ^15.5" @@ -38908,7 +39093,7 @@ __metadata: parse-multipart-data: "npm:^1.4.0" performance-now: "npm:^2.1.0" portfinder: "npm:^1.0.32" - read-tls-client-hello: "npm:^1.0.0" + read-tls-client-hello: "npm:^1.1.0" semver: "npm:^7.5.3" socks-proxy-agent: "npm:^7.0.0" typed-error: "npm:^3.0.2" @@ -38917,7 +39102,7 @@ __metadata: ws: "npm:^8.8.0" bin: mockttp: dist/admin/admin-bin.js - checksum: 10/93eca69baa35db11ae7f541d712bfd208edf0017db3091e874f59fc64a7c036179906b4e9b66936f054ad1c258645d085452dafab58a1c2df9fd6d67330f8328 + checksum: 10/09278221361b9896636bbf9e6a6d6427afe2375c035dcc6ddd56d308a7b197ae3a5196fba7b00b9fb1bfb0b4c22daacc046bdc207e0ab9ef15bb2a79637cdfbf languageName: node linkType: hard @@ -39119,19 +39304,19 @@ __metadata: linkType: hard "mysql2@npm:^3.0.0": - version: 3.11.5 - resolution: "mysql2@npm:3.11.5" + version: 3.15.3 + resolution: "mysql2@npm:3.15.3" dependencies: aws-ssl-profiles: "npm:^1.1.1" denque: "npm:^2.1.0" generate-function: "npm:^2.3.1" - iconv-lite: "npm:^0.6.3" + iconv-lite: "npm:^0.7.0" long: "npm:^5.2.1" lru.min: "npm:^1.0.0" named-placeholders: "npm:^1.1.3" seq-queue: "npm:^0.0.5" sqlstring: "npm:^2.3.2" - checksum: 10/912dc364f6f9684721add5474c732f5d7c2403ec17cb05b06f95adafab3cb32daa2798b283d48d0951d11af94a58558581ed22635803fc6baab651d3651c1cc9 + checksum: 10/96fbab423afb05a9ac397c272aa1097872797e224b7d1785ac05b7e46d0884c1e613ad9e2c3082ad7973f229357f9ab5824a7f90e8c582b2094ce52027fff8e3 languageName: node linkType: hard @@ -39183,7 +39368,7 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.3.11, nanoid@npm:^3.3.7": +"nanoid@npm:^3.3.11": version: 3.3.11 resolution: "nanoid@npm:3.3.11" bin: @@ -39192,10 +39377,19 @@ __metadata: languageName: node linkType: hard -"napi-build-utils@npm:^1.0.1": - version: 1.0.2 - resolution: "napi-build-utils@npm:1.0.2" - checksum: 10/276feb8e30189fe18718e85b6f82e4f952822baa2e7696f771cc42571a235b789dc5907a14d9ffb6838c3e4ff4c25717c2575e5ce1cf6e02e496e204c11e57f6 +"napi-build-utils@npm:^2.0.0": + version: 2.0.0 + resolution: "napi-build-utils@npm:2.0.0" + checksum: 10/69adcdb828481737f1ec64440286013f6479d5b264e24d5439ba795f65293d0bb6d962035de07c65fae525ed7d2fcd0baab6891d8e3734ea792fec43918acf83 + languageName: node + linkType: hard + +"napi-postinstall@npm:^0.3.0": + version: 0.3.4 + resolution: "napi-postinstall@npm:0.3.4" + bin: + napi-postinstall: lib/cli.js + checksum: 10/5541381508f9e1051ff3518701c7130ebac779abb3a1ffe9391fcc3cab4cc0569b0ba0952357db3f6b12909c3bb508359a7a60261ffd795feebbdab967175832 languageName: node linkType: hard @@ -39442,10 +39636,10 @@ __metadata: languageName: node linkType: hard -"node-forge@npm:^1, node-forge@npm:^1.2.1, node-forge@npm:^1.3.1": - version: 1.3.1 - resolution: "node-forge@npm:1.3.1" - checksum: 10/05bab6868633bf9ad4c3b1dd50ec501c22ffd69f556cdf169a00998ca1d03e8107a6032ba013852f202035372021b845603aeccd7dfcb58cdb7430013b3daa8d +"node-forge@npm:^1, node-forge@npm:^1.2.1, node-forge@npm:^1.3.2": + version: 1.3.3 + resolution: "node-forge@npm:1.3.3" + checksum: 10/f41c31b9296771a4b8c955d58417471712f54f324603a35f8e6cbac19d5e6eaaf5fd5fd14584dfedecbf46a05438ded6eee60a5f2f0822fc5061aaa073cfc75d languageName: node linkType: hard @@ -39586,10 +39780,10 @@ __metadata: languageName: node linkType: hard -"node-releases@npm:^2.0.18": - version: 2.0.18 - resolution: "node-releases@npm:2.0.18" - checksum: 10/241e5fa9556f1c12bafb83c6c3e94f8cf3d8f2f8f904906ecef6e10bcaa1d59aa61212d4651bec70052015fc54bd3fdcdbe7fc0f638a17e6685aa586c076ec4e +"node-releases@npm:^2.0.27": + version: 2.0.27 + resolution: "node-releases@npm:2.0.27" + checksum: 10/f6c78ddb392ae500719644afcbe68a9ea533242c02312eb6a34e8478506eb7482a3fb709c70235b01c32fe65625b68dfa9665113f816d87f163bc3819b62b106 languageName: node linkType: hard @@ -39676,9 +39870,9 @@ __metadata: linkType: hard "nodemailer@npm:^7.0.7": - version: 7.0.10 - resolution: "nodemailer@npm:7.0.10" - checksum: 10/b9b8794ffc6c0d84440a9dd422664908e9c2003a15cb0e6bdd240a3625121de3978323c2ba4af78080fd735ef514d6caed376bd5f5dd6c17cf0d2c399d0dc354 + version: 7.0.11 + resolution: "nodemailer@npm:7.0.11" + checksum: 10/2ad4dd56a4caf84a83aa6f4378ded26d5ef8a644ca3be09c3b4fb2255d861369e620f29be6c3c97148ac4a50aa5fdff6240b9d60805362bd99ca15f2ea62e8a2 languageName: node linkType: hard @@ -39987,10 +40181,10 @@ __metadata: languageName: node linkType: hard -"nwsapi@npm:^2.2.0, nwsapi@npm:^2.2.12": - version: 2.2.13 - resolution: "nwsapi@npm:2.2.13" - checksum: 10/f7f30a236f2ee513ea8042f1a987481dc2b900167c47f7163882f0fcfe7ccb57b5c8daaf2c91008dc20a204fcd79e050aee25001433ad99990bbed5a8c74121c +"nwsapi@npm:^2.2.12": + version: 2.2.23 + resolution: "nwsapi@npm:2.2.23" + checksum: 10/aa4a570039c33d70b51436d1bb533f3e2c33c488ccbe9b09285c46a6cee5ef266fd60103461085c6954ba52460786a8138f042958328c7c1b4763898eb3dadfa languageName: node linkType: hard @@ -40036,10 +40230,10 @@ __metadata: languageName: node linkType: hard -"object-inspect@npm:^1.13.3": - version: 1.13.3 - resolution: "object-inspect@npm:1.13.3" - checksum: 10/14cb973d8381c69e14d7f1c8c75044eb4caf04c6dabcf40ca5c2ce42dc2073ae0bb2a9939eeca142b0c05215afaa1cd5534adb7c8879c32cba2576e045ed8368 +"object-inspect@npm:^1.13.3, object-inspect@npm:^1.13.4": + version: 1.13.4 + resolution: "object-inspect@npm:1.13.4" + checksum: 10/aa13b1190ad3e366f6c83ad8a16ed37a19ed57d267385aa4bfdccda833d7b90465c057ff6c55d035a6b2e52c1a2295582b294217a0a3a1ae7abdd6877ef781fb languageName: node linkType: hard @@ -40109,7 +40303,7 @@ __metadata: languageName: node linkType: hard -"object.values@npm:^1.1.6, object.values@npm:^1.2.0, object.values@npm:^1.2.1": +"object.values@npm:^1.1.6, object.values@npm:^1.2.1": version: 1.2.1 resolution: "object.values@npm:1.2.1" dependencies: @@ -40163,7 +40357,7 @@ __metadata: languageName: node linkType: hard -"on-finished@npm:2.4.1, on-finished@npm:^2.3.0, on-finished@npm:^2.4.1": +"on-finished@npm:2.4.1, on-finished@npm:^2.3.0, on-finished@npm:^2.4.1, on-finished@npm:~2.4.1": version: 2.4.1 resolution: "on-finished@npm:2.4.1" dependencies: @@ -40258,19 +40452,19 @@ __metadata: languageName: node linkType: hard -"open@npm:^10.0.3": - version: 10.0.3 - resolution: "open@npm:10.0.3" +"open@npm:^10.0.3, open@npm:^10.2.0": + version: 10.2.0 + resolution: "open@npm:10.2.0" dependencies: default-browser: "npm:^5.2.1" define-lazy-prop: "npm:^3.0.0" is-inside-container: "npm:^1.0.0" - is-wsl: "npm:^3.1.0" - checksum: 10/4dc757ad1d3d63490822f991e9cbe3a7c05b7249fca2eaa571cb7d191e5cec88bc37e15d8ef4fd740d8989a288b661d8da253caa8d98e8c97430ddbbb0ae4ed1 + wsl-utils: "npm:^0.1.0" + checksum: 10/e6ad9474734eac3549dcc7d85e952394856ccaee48107c453bd6a725b82e3b8ed5f427658935df27efa76b411aeef62888edea8a9e347e8e7c82632ec966b30e languageName: node linkType: hard -"open@npm:^8.0.0, open@npm:^8.0.4, open@npm:^8.4.0": +"open@npm:^8.0.0, open@npm:^8.4.0": version: 8.4.2 resolution: "open@npm:8.4.2" dependencies: @@ -40478,6 +40672,86 @@ __metadata: languageName: node linkType: hard +"own-keys@npm:^1.0.1": + version: 1.0.1 + resolution: "own-keys@npm:1.0.1" + dependencies: + get-intrinsic: "npm:^1.2.6" + object-keys: "npm:^1.1.1" + safe-push-apply: "npm:^1.0.0" + checksum: 10/ab4bb3b8636908554fc19bf899e225444195092864cb61503a0d048fdaf662b04be2605b636a4ffeaf6e8811f6fcfa8cbb210ec964c0eb1a41eb853e1d5d2f41 + languageName: node + linkType: hard + +"oxc-resolver@npm:^11.13.2": + version: 11.14.2 + resolution: "oxc-resolver@npm:11.14.2" + dependencies: + "@oxc-resolver/binding-android-arm-eabi": "npm:11.14.2" + "@oxc-resolver/binding-android-arm64": "npm:11.14.2" + "@oxc-resolver/binding-darwin-arm64": "npm:11.14.2" + "@oxc-resolver/binding-darwin-x64": "npm:11.14.2" + "@oxc-resolver/binding-freebsd-x64": "npm:11.14.2" + "@oxc-resolver/binding-linux-arm-gnueabihf": "npm:11.14.2" + "@oxc-resolver/binding-linux-arm-musleabihf": "npm:11.14.2" + "@oxc-resolver/binding-linux-arm64-gnu": "npm:11.14.2" + "@oxc-resolver/binding-linux-arm64-musl": "npm:11.14.2" + "@oxc-resolver/binding-linux-ppc64-gnu": "npm:11.14.2" + "@oxc-resolver/binding-linux-riscv64-gnu": "npm:11.14.2" + "@oxc-resolver/binding-linux-riscv64-musl": "npm:11.14.2" + "@oxc-resolver/binding-linux-s390x-gnu": "npm:11.14.2" + "@oxc-resolver/binding-linux-x64-gnu": "npm:11.14.2" + "@oxc-resolver/binding-linux-x64-musl": "npm:11.14.2" + "@oxc-resolver/binding-openharmony-arm64": "npm:11.14.2" + "@oxc-resolver/binding-wasm32-wasi": "npm:11.14.2" + "@oxc-resolver/binding-win32-arm64-msvc": "npm:11.14.2" + "@oxc-resolver/binding-win32-ia32-msvc": "npm:11.14.2" + "@oxc-resolver/binding-win32-x64-msvc": "npm:11.14.2" + dependenciesMeta: + "@oxc-resolver/binding-android-arm-eabi": + optional: true + "@oxc-resolver/binding-android-arm64": + optional: true + "@oxc-resolver/binding-darwin-arm64": + optional: true + "@oxc-resolver/binding-darwin-x64": + optional: true + "@oxc-resolver/binding-freebsd-x64": + optional: true + "@oxc-resolver/binding-linux-arm-gnueabihf": + optional: true + "@oxc-resolver/binding-linux-arm-musleabihf": + optional: true + "@oxc-resolver/binding-linux-arm64-gnu": + optional: true + "@oxc-resolver/binding-linux-arm64-musl": + optional: true + "@oxc-resolver/binding-linux-ppc64-gnu": + optional: true + "@oxc-resolver/binding-linux-riscv64-gnu": + optional: true + "@oxc-resolver/binding-linux-riscv64-musl": + optional: true + "@oxc-resolver/binding-linux-s390x-gnu": + optional: true + "@oxc-resolver/binding-linux-x64-gnu": + optional: true + "@oxc-resolver/binding-linux-x64-musl": + optional: true + "@oxc-resolver/binding-openharmony-arm64": + optional: true + "@oxc-resolver/binding-wasm32-wasi": + optional: true + "@oxc-resolver/binding-win32-arm64-msvc": + optional: true + "@oxc-resolver/binding-win32-ia32-msvc": + optional: true + "@oxc-resolver/binding-win32-x64-msvc": + optional: true + checksum: 10/f64fa5cd61700b0ba370319f87b6c5ef45b9036f2c21719a563999285493f8152d9c7922d35d07208088d3893327de91c10e65cbc4131782aeeb9f9da365ae7c + languageName: node + linkType: hard + "p-cancelable@npm:^1.0.0": version: 1.1.0 resolution: "p-cancelable@npm:1.1.0" @@ -40547,15 +40821,6 @@ __metadata: languageName: node linkType: hard -"p-limit@npm:^4.0.0": - version: 4.0.0 - resolution: "p-limit@npm:4.0.0" - dependencies: - yocto-queue: "npm:^1.0.0" - checksum: 10/01d9d70695187788f984226e16c903475ec6a947ee7b21948d6f597bed788e3112cc7ec2e171c1d37125057a5f45f3da21d8653e04a3a793589e12e9e80e756b - languageName: node - linkType: hard - "p-locate@npm:^3.0.0": version: 3.0.0 resolution: "p-locate@npm:3.0.0" @@ -40583,15 +40848,6 @@ __metadata: languageName: node linkType: hard -"p-locate@npm:^6.0.0": - version: 6.0.0 - resolution: "p-locate@npm:6.0.0" - dependencies: - p-limit: "npm:^4.0.0" - checksum: 10/2bfe5234efa5e7a4e74b30a5479a193fdd9236f8f6b4d2f3f69e3d286d9a7d7ab0c118a2a50142efcf4e41625def635bd9332d6cbf9cc65d85eb0718c579ab38 - languageName: node - linkType: hard - "p-map@npm:^2.0.0": version: 2.1.0 resolution: "p-map@npm:2.1.0" @@ -40853,6 +41109,21 @@ __metadata: languageName: node linkType: hard +"parse-entities@npm:^4.0.0": + version: 4.0.2 + resolution: "parse-entities@npm:4.0.2" + dependencies: + "@types/unist": "npm:^2.0.0" + character-entities-legacy: "npm:^3.0.0" + character-reference-invalid: "npm:^2.0.0" + decode-named-character-reference: "npm:^1.0.0" + is-alphanumerical: "npm:^2.0.0" + is-decimal: "npm:^2.0.0" + is-hexadecimal: "npm:^2.0.0" + checksum: 10/b0ce693d0b3d7ed1cea6fe814e6e077c71532695f01178e846269e9a2bc2f7ff34ca4bb8db80b48af0451100f25bb010df6591c9bb6306e4680ccb423d1e4038 + languageName: node + linkType: hard + "parse-json@npm:^5.0.0, parse-json@npm:^5.2.0": version: 5.2.0 resolution: "parse-json@npm:5.2.0" @@ -40872,13 +41143,6 @@ __metadata: languageName: node linkType: hard -"parse-ms@npm:^4.0.0": - version: 4.0.0 - resolution: "parse-ms@npm:4.0.0" - checksum: 10/673c801d9f957ff79962d71ed5a24850163f4181a90dd30c4e3666b3a804f53b77f1f0556792e8b2adbb5d58757907d1aa51d7d7dc75997c2a56d72937cbc8b7 - languageName: node - linkType: hard - "parse-multipart-data@npm:^1.4.0": version: 1.5.0 resolution: "parse-multipart-data@npm:1.5.0" @@ -40912,11 +41176,20 @@ __metadata: linkType: hard "parse5@npm:^7.0.0, parse5@npm:^7.1.2": - version: 7.2.0 - resolution: "parse5@npm:7.2.0" + version: 7.3.0 + resolution: "parse5@npm:7.3.0" dependencies: - entities: "npm:^4.5.0" - checksum: 10/49dabfe848f00e8cad8d9198a094d667fbdecbfa5143ddf8fb708e499b5ba76426c16135c8993b1d8e01827b92e8cfab0a9a248afa6ad7cc6f38aecf5bd017e6 + entities: "npm:^6.0.0" + checksum: 10/b0e48be20b820c655b138b86fa6fb3a790de6c891aa2aba536524f8027b4dca4fe538f11a0e5cf2f6f847d120dbb9e4822dcaeb933ff1e10850a2ef0154d1d88 + languageName: node + linkType: hard + +"parse5@npm:^8.0.0": + version: 8.0.0 + resolution: "parse5@npm:8.0.0" + dependencies: + entities: "npm:^6.0.0" + checksum: 10/1973850932bb1cbd52ab64502761489fbe1bb43a52dee7ce41aac0b6c33a51a92aaee04661590b0912b739ae9ee316bce4c78c8ea34af42a7e522c983c3c6cf5 languageName: node linkType: hard @@ -41101,13 +41374,6 @@ __metadata: languageName: node linkType: hard -"path-exists@npm:^5.0.0": - version: 5.0.0 - resolution: "path-exists@npm:5.0.0" - checksum: 10/8ca842868cab09423994596eb2c5ec2a971c17d1a3cb36dbf060592c730c725cd524b9067d7d2a1e031fef9ba7bd2ac6dc5ec9fb92aa693265f7be3987045254 - languageName: node - linkType: hard - "path-is-absolute@npm:^1.0.0": version: 1.0.1 resolution: "path-is-absolute@npm:1.0.1" @@ -41170,13 +41436,6 @@ __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-to-regexp@npm:3.3.0": version: 3.3.0 resolution: "path-to-regexp@npm:3.3.0" @@ -41198,6 +41457,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" @@ -41212,6 +41478,13 @@ __metadata: languageName: node linkType: hard +"pathe@npm:^2.0.3": + version: 2.0.3 + resolution: "pathe@npm:2.0.3" + checksum: 10/01e9a69928f39087d96e1751ce7d6d50da8c39abf9a12e0ac2389c42c83bc76f78c45a475bd9026a02e6a6f79be63acc75667df855862fe567d99a00a540d23d + languageName: node + linkType: hard + "pathval@npm:^2.0.0": version: 2.0.0 resolution: "pathval@npm:2.0.0" @@ -41277,10 +41550,10 @@ __metadata: languageName: node linkType: hard -"pg-cloudflare@npm:^1.1.1": - version: 1.1.1 - resolution: "pg-cloudflare@npm:1.1.1" - checksum: 10/45ca0c7926967ec9e66a9efc73ca57e3e933671b541bc774631a02ce683e7f658d0a4e881119b3f61486f38e344ae1b008d3a20eb5e21701c5fa8ff8382c5538 +"pg-cloudflare@npm:^1.2.7": + version: 1.2.7 + resolution: "pg-cloudflare@npm:1.2.7" + checksum: 10/3d171407cbce36436c461200666ba6bd884bfe98016972760a797cec850199b8024a40055d80322c5fc02909e1533a144e9b108a99f7d7e21d0c42612f9821fb languageName: node linkType: hard @@ -41291,10 +41564,10 @@ __metadata: languageName: node linkType: hard -"pg-connection-string@npm:^2.3.0, pg-connection-string@npm:^2.5.0, pg-connection-string@npm:^2.7.0": - version: 2.7.0 - resolution: "pg-connection-string@npm:2.7.0" - checksum: 10/68015a8874b7ca5dad456445e4114af3d2602bac2fdb8069315ecad0ff9660ec93259b9af7186606529ac4f6f72a06831e6f20897a689b16cc7fda7ca0e247fd +"pg-connection-string@npm:^2.3.0, pg-connection-string@npm:^2.5.0, pg-connection-string@npm:^2.9.1": + version: 2.9.1 + resolution: "pg-connection-string@npm:2.9.1" + checksum: 10/40e9e9cd752121e72bff18d83e6c7ecda9056426815a84294de018569a319293c924704c8b7f0604fdc588835c7927647dea4f3c87a014e715bcbb17d794e9f0 languageName: node linkType: hard @@ -41312,23 +41585,23 @@ __metadata: languageName: node linkType: hard -"pg-pool@npm:^3.8.0": - version: 3.8.0 - resolution: "pg-pool@npm:3.8.0" +"pg-pool@npm:^3.10.1": + version: 3.10.1 + resolution: "pg-pool@npm:3.10.1" peerDependencies: pg: ">=8.0" - checksum: 10/be6b7c6932fa177dc69ca8980d959f24b4979911465ac11656b8ff86707cba035e776416d8aac1029e8cc4d8eb8989ed2cc85a33ac6b76e266b20a81284436d8 + checksum: 10/b389a714be59ebe53ec412cbff513191cc0b7a203faa5d26416b6a038cafdfe30fbf1a5936b77bb76109c49bd7c4a116870a5a46a45796b1b34c96f016d7fbe2 languageName: node linkType: hard -"pg-protocol@npm:*, pg-protocol@npm:^1.8.0": - version: 1.8.0 - resolution: "pg-protocol@npm:1.8.0" - checksum: 10/52f67d8161ae4afb1dbf96f6ad12a2ecf478dbb0b80baa239047cd562dee378961fd446f0a1cfc1fd323e052fbb3df47e886c5d9d86a2803a000d36682b29094 +"pg-protocol@npm:*, pg-protocol@npm:^1.10.3": + version: 1.10.3 + resolution: "pg-protocol@npm:1.10.3" + checksum: 10/31da85319084c03f403efee7accce9786964df82a7feb60e6bd77b71f1e622c74a2a644a2bc434389d0ab92e5abdeedea69ebdb53b1897d9f01d2a1f51a8a2fe languageName: node linkType: hard -"pg-types@npm:^2.1.0, pg-types@npm:^2.2.0": +"pg-types@npm:2.2.0, pg-types@npm:^2.2.0": version: 2.2.0 resolution: "pg-types@npm:2.2.0" dependencies: @@ -41342,15 +41615,15 @@ __metadata: linkType: hard "pg@npm:^8.11.3, pg@npm:^8.9.0": - version: 8.14.1 - resolution: "pg@npm:8.14.1" + version: 8.16.3 + resolution: "pg@npm:8.16.3" dependencies: - pg-cloudflare: "npm:^1.1.1" - pg-connection-string: "npm:^2.7.0" - pg-pool: "npm:^3.8.0" - pg-protocol: "npm:^1.8.0" - pg-types: "npm:^2.1.0" - pgpass: "npm:1.x" + pg-cloudflare: "npm:^1.2.7" + pg-connection-string: "npm:^2.9.1" + pg-pool: "npm:^3.10.1" + pg-protocol: "npm:^1.10.3" + pg-types: "npm:2.2.0" + pgpass: "npm:1.0.5" peerDependencies: pg-native: ">=3.0.1" dependenciesMeta: @@ -41359,16 +41632,16 @@ __metadata: peerDependenciesMeta: pg-native: optional: true - checksum: 10/45f2d5719fd74a6a4784c5115c0ff482af92d1e5b101bf423160b6a983e37cc2fad4a7eea2a06f27e6f8bdb8abce23486d2d522c8c52c90f68a2bc897f0553c4 + checksum: 10/6a2885a3f581d6c6dddddf5a4bb2790ee84f402ed7d73ece8b6bc102c58c17e4c5f17894c241633aa2f1d4fedd8f2401a80a9a02ef18bb57d05cbbfd8a53ca4d languageName: node linkType: hard -"pgpass@npm:1.x": - version: 1.0.2 - resolution: "pgpass@npm:1.0.2" +"pgpass@npm:1.0.5": + version: 1.0.5 + resolution: "pgpass@npm:1.0.5" dependencies: - split: "npm:^1.0.0" - checksum: 10/4f09d81d5aa9e3e409e2a58dc71f889ca92233d66772adcb810e53e637b49013ae84b8dd831aa5e6f1413bffb4b9b1eafd1a05227c8cf05c39f5cd276d4e9492 + split2: "npm:^4.1.0" + checksum: 10/0a6f3bf76e36bdb3c20a7e8033140c732767bba7e81f845f7489fc3123a2bd6e3b8e704f08cba86b117435414b5d2422e20ba9d5f2efb6f0c75c9efca73e8e87 languageName: node linkType: hard @@ -41500,7 +41773,7 @@ __metadata: languageName: node linkType: hard -"pirates@npm:^4.0.1, pirates@npm:^4.0.4, pirates@npm:^4.0.6": +"pirates@npm:^4.0.1, pirates@npm:^4.0.6, pirates@npm:^4.0.7": version: 4.0.7 resolution: "pirates@npm:4.0.7" checksum: 10/2427f371366081ae42feb58214f04805d6b41d6b84d74480ebcc9e0ddbd7105a139f7c653daeaf83ad8a1a77214cf07f64178e76de048128fec501eab3305a96 @@ -41564,27 +41837,27 @@ __metadata: languageName: node linkType: hard -"playwright-core@npm:1.56.1": - version: 1.56.1 - resolution: "playwright-core@npm:1.56.1" +"playwright-core@npm:1.57.0": + version: 1.57.0 + resolution: "playwright-core@npm:1.57.0" bin: playwright-core: cli.js - checksum: 10/df785eb3b3a8392b10dcde5f768e09b7fe459a7b06ed81180da69e048f2154b761f86d79572c2b62037a1f18a44e4ace72f5b6547f4f473b4ab13ab1d94007d2 + checksum: 10/ec066602f0196f036006caee14a30d0a57533a76673bb9a0c609ef56e21decf018f0e8d402ba2fb18251393be6a1c9e193c83266f1670fe50838c5340e220de0 languageName: node linkType: hard -"playwright@npm:1.56.1": - version: 1.56.1 - resolution: "playwright@npm:1.56.1" +"playwright@npm:1.57.0": + version: 1.57.0 + resolution: "playwright@npm:1.57.0" dependencies: fsevents: "npm:2.3.2" - playwright-core: "npm:1.56.1" + playwright-core: "npm:1.57.0" dependenciesMeta: fsevents: optional: true bin: playwright: cli.js - checksum: 10/f1743f93b26f1d497257771428d93f3c9ed2d75b00d935f0cd1556ff2dc61d47f2df8b381d752fbd2c47082b685f0ffe4cc4b7ba440d7b4ba3a08572aec58fba + checksum: 10/241559210f98ef11b6bd6413f2d29da7ef67c7865b72053192f0d164fab9e0d3bd47913b3351d5de6433a8aff2d8424d4b8bd668df420bf4dda7ae9fcd37b942 languageName: node linkType: hard @@ -42139,16 +42412,16 @@ __metadata: languageName: node linkType: hard -"prebuild-install@npm:^7.1.1, prebuild-install@npm:^7.1.2": - version: 7.1.2 - resolution: "prebuild-install@npm:7.1.2" +"prebuild-install@npm:^7.1.1, prebuild-install@npm:^7.1.3": + version: 7.1.3 + resolution: "prebuild-install@npm:7.1.3" dependencies: detect-libc: "npm:^2.0.0" expand-template: "npm:^2.0.3" github-from-package: "npm:0.0.0" minimist: "npm:^1.2.3" mkdirp-classic: "npm:^0.5.3" - napi-build-utils: "npm:^1.0.1" + napi-build-utils: "npm:^2.0.0" node-abi: "npm:^3.3.0" pump: "npm:^3.0.0" rc: "npm:^1.2.7" @@ -42157,7 +42430,7 @@ __metadata: tunnel-agent: "npm:^0.6.0" bin: prebuild-install: bin.js - checksum: 10/32d5c026cc978dd02762b9ad3c765178aee8383aeac4303fed3cd226eff53100db038d4791b03ae1ebc7d213a7af392d26e32095579cedb8dba1d00ad08ecd46 + checksum: 10/1b7e4c00d2750b532a4fc2a83ffb0c5fefa1b6f2ad071896ead15eeadc3255f5babd816949991af083cf7429e375ae8c7d1c51f73658559da36f948a020a3a11 languageName: node linkType: hard @@ -42252,6 +42525,17 @@ __metadata: languageName: node linkType: hard +"pretty-format@npm:30.2.0, pretty-format@npm:^30.0.0": + version: 30.2.0 + resolution: "pretty-format@npm:30.2.0" + dependencies: + "@jest/schemas": "npm:30.0.5" + ansi-styles: "npm:^5.2.0" + react-is: "npm:^18.3.1" + checksum: 10/725890d648e3400575eebc99a334a4cd1498e0d36746313913706bbeea20ada27e17c184a3cd45c50f705c16111afa829f3450233fc0fda5eed293c69757e926 + languageName: node + linkType: hard + "pretty-format@npm:^27.0.2": version: 27.5.1 resolution: "pretty-format@npm:27.5.1" @@ -42263,7 +42547,7 @@ __metadata: languageName: node linkType: hard -"pretty-format@npm:^29.0.0, pretty-format@npm:^29.7.0": +"pretty-format@npm:^29.7.0": version: 29.7.0 resolution: "pretty-format@npm:29.7.0" dependencies: @@ -42283,15 +42567,6 @@ __metadata: languageName: node linkType: hard -"pretty-ms@npm:^9.0.0": - version: 9.2.0 - resolution: "pretty-ms@npm:9.2.0" - dependencies: - parse-ms: "npm:^4.0.0" - checksum: 10/a65a1d81560867f4f7128862fdbf0e1c2d3c5607bf75cae7758bf8111e2c4b744be46e084704125a38ba918bb43defa7a53aaff0f48c5c2d95367d3148c980d9 - languageName: node - linkType: hard - "printj@npm:~1.1.0": version: 1.1.2 resolution: "printj@npm:1.1.2" @@ -42449,7 +42724,7 @@ __metadata: languageName: node linkType: hard -"prompts@npm:^2.0.1, prompts@npm:^2.4.2": +"prompts@npm:^2.4.2": version: 2.4.2 resolution: "prompts@npm:2.4.2" dependencies: @@ -42506,6 +42781,13 @@ __metadata: languageName: node linkType: hard +"property-information@npm:^7.0.0": + version: 7.1.0 + resolution: "property-information@npm:7.1.0" + checksum: 10/896d38a52ad7170de73f832d277c69e76a9605d941ebb3f0d6e56271414a7fdf95ff6d2819e68036b8a0c7d2d4d88bf1d4a5765c032cb19c2343567ee3a14b15 + languageName: node + linkType: hard + "proto3-json-serializer@npm:^2.0.2": version: 2.0.2 resolution: "proto3-json-serializer@npm:2.0.2" @@ -42575,13 +42857,6 @@ __metadata: languageName: node linkType: hard -"psl@npm:^1.1.33": - version: 1.8.0 - resolution: "psl@npm:1.8.0" - checksum: 10/5f62a8eca06cb4a017983d15b92b0d38dc8699d637eabc8cb482c59b4106c9760f59cc8afabcb8bb7b98f0322907680d8f0f59226386fffab5248d180bc04578 - languageName: node - linkType: hard - "pstree.remy@npm:^1.1.8": version: 1.1.8 resolution: "pstree.remy@npm:1.1.8" @@ -42650,23 +42925,14 @@ __metadata: languageName: node linkType: hard -"pure-rand@npm:^6.0.0": - version: 6.0.4 - resolution: "pure-rand@npm:6.0.4" - checksum: 10/34fed0abe99d3db7ddc459c12e1eda6bff05db6a17f2017a1ae12202271ccf276fb223b442653518c719671c1b339bbf97f27ba9276dba0997c89e45c4e6a3bf +"pure-rand@npm:^7.0.0": + version: 7.0.1 + resolution: "pure-rand@npm:7.0.1" + checksum: 10/c61a576fda5032ec9763ecb000da4a8f19263b9e2f9ae9aa2759c8fbd9dc6b192b2ce78391ebd41abb394a5fedb7bcc4b03c9e6141ac8ab20882dd5717698b80 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.10.1, qs@npm:^6.10.3, qs@npm:^6.11.2, qs@npm:^6.12.2, qs@npm:^6.12.3, qs@npm:^6.14.0, qs@npm:^6.7.0, qs@npm:^6.9.4": +"qs@npm:^6.10.1, qs@npm:^6.10.3, qs@npm:^6.11.2, qs@npm:^6.12.2, qs@npm:^6.12.3, qs@npm:^6.14.0, qs@npm:^6.7.0, qs@npm:^6.9.4, qs@npm:~6.14.0": version: 6.14.0 resolution: "qs@npm:6.14.0" dependencies: @@ -42828,11 +43094,11 @@ __metadata: linkType: hard "rate-limit-redis@npm:^4.2.0": - version: 4.2.3 - resolution: "rate-limit-redis@npm:4.2.3" + version: 4.3.1 + resolution: "rate-limit-redis@npm:4.3.1" peerDependencies: express-rate-limit: ">= 6" - checksum: 10/00c95868e48214b926272b2f01418695acd0487f766d36fec68992922037d43e67eeacc6854f38b8a511c69b9385b2de6994d08802a6aa422686dfd4c55e5ba0 + checksum: 10/6e02374832ced889a61829e327d03d021c50c0fc3ac86674d8c922cd3a50c0451091106811c308ed5553776a28b6acbc5313e2c9d37aecb198ab306f249de21b languageName: node linkType: hard @@ -42843,15 +43109,15 @@ __metadata: languageName: node linkType: hard -"raw-body@npm:2.5.2, raw-body@npm:^2.3.3, raw-body@npm:^2.4.1": - version: 2.5.2 - resolution: "raw-body@npm:2.5.2" +"raw-body@npm:^2.3.3, raw-body@npm:^2.4.1, 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 @@ -43126,9 +43392,9 @@ __metadata: languageName: node linkType: hard -"react-docgen@npm:^8.0.0": - version: 8.0.1 - resolution: "react-docgen@npm:8.0.1" +"react-docgen@npm:^8.0.0, react-docgen@npm:^8.0.2": + version: 8.0.2 + resolution: "react-docgen@npm:8.0.2" dependencies: "@babel/core": "npm:^7.28.0" "@babel/traverse": "npm:^7.28.0" @@ -43140,7 +43406,7 @@ __metadata: doctrine: "npm:^3.0.0" resolve: "npm:^1.22.1" strip-indent: "npm:^4.0.0" - checksum: 10/6eb9ec3870c0f527fd05b048a842e2338825d99703312d6f4e84f3ae0e5eda427e6b4c7c07a93b21d8740e2be23e779e0237c4b2783160e285f0121711e2f47c + checksum: 10/b56f594237a0bdf1356dee6a416ab6f2f38f60a7330cbdaf1da93d366f29f6bfbbfedecd51d47f6ba2a898985c3205c369e7f6cad528478560d9363717243ff8 languageName: node linkType: hard @@ -43281,11 +43547,11 @@ __metadata: linkType: hard "react-hook-form@npm:^7.12.2": - version: 7.55.0 - resolution: "react-hook-form@npm:7.55.0" + version: 7.67.0 + resolution: "react-hook-form@npm:7.67.0" peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 - checksum: 10/900e06064c1b4977f2e3e249a0f91742ae6bd1f2109701b80a9280b1c2b4bee8aac26aed7104371101eadad724f0bbe0215499e84623e6375007489ae850fe8e + checksum: 10/0e74a48b2da0f79166d5c6701f78a08fd5165e901e46ce5a578f0a2f25005e861023ffb35c1a96373c23701a84c3fce2111c4aced824d2ca08c9b2420a645551 languageName: node linkType: hard @@ -43344,7 +43610,7 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^18.0.0, react-is@npm:^18.2.0": +"react-is@npm:^18.0.0, react-is@npm:^18.2.0, react-is@npm:^18.3.1": version: 18.3.1 resolution: "react-is@npm:18.3.1" checksum: 10/d5f60c87d285af24b1e1e7eaeb123ec256c3c8bdea7061ab3932e3e14685708221bf234ec50b21e10dd07f008f1b966a2730a0ce4ff67905b3872ff2042aec22 @@ -43680,7 +43946,7 @@ __metadata: languageName: node linkType: hard -"react-syntax-highlighter@npm:^15.4.5, react-syntax-highlighter@npm:^15.6.1": +"react-syntax-highlighter@npm:^15.4.5": version: 15.6.6 resolution: "react-syntax-highlighter@npm:15.6.6" dependencies: @@ -43696,6 +43962,22 @@ __metadata: languageName: node linkType: hard +"react-syntax-highlighter@npm:^16.0.0": + version: 16.1.0 + resolution: "react-syntax-highlighter@npm:16.1.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + highlight.js: "npm:^10.4.1" + highlightjs-vue: "npm:^1.0.0" + lowlight: "npm:^1.17.0" + prismjs: "npm:^1.30.0" + refractor: "npm:^5.0.0" + peerDependencies: + react: ">= 0.14.0" + checksum: 10/d55bc96802357de2102703fb3ffe86a67d52099e93da9624a900609e4d8b6f5f95472d42423951631348fdee2679d661b17aaf41039035dc3f1bbabf0423d62e + languageName: node + linkType: hard + "react-test-renderer@npm:^16.13.1": version: 16.14.0 resolution: "react-test-renderer@npm:16.14.0" @@ -43749,8 +44031,8 @@ __metadata: linkType: hard "react-use@npm:^17.2.4, react-use@npm:^17.3.2, react-use@npm:^17.4.0": - version: 17.5.1 - resolution: "react-use@npm:17.5.1" + version: 17.6.0 + resolution: "react-use@npm:17.6.0" dependencies: "@types/js-cookie": "npm:^2.2.6" "@xobotyi/scrollbar-width": "npm:^1.9.5" @@ -43769,7 +44051,7 @@ __metadata: peerDependencies: react: "*" react-dom: "*" - checksum: 10/2da403a9949dbd964b9b8e20dcd354db66b7f7d5ca1f42572fbcdb06bd49ee828c295be4912cb87abc163d1b54820bb8c5fa85314a16c4579d9e30bf9cbd5759 + checksum: 10/a817b74e82b481a39d3539bfe8d3b535c08d59d44a75ea91f65e56a7ccaedb0de185159e50b44ea4a635dda0c1c7159f07530e81a1d64b57130e0a715a107795 languageName: node linkType: hard @@ -43839,12 +44121,12 @@ __metadata: languageName: node linkType: hard -"read-tls-client-hello@npm:^1.0.0": - version: 1.0.1 - resolution: "read-tls-client-hello@npm:1.0.1" +"read-tls-client-hello@npm:^1.0.0, read-tls-client-hello@npm:^1.1.0": + version: 1.1.0 + resolution: "read-tls-client-hello@npm:1.1.0" dependencies: "@types/node": "npm:*" - checksum: 10/fefa63605eebb197b6046cb26d7efde16eb24c026edbbca789c30b7b5be7c0dd8e45fb4d32707513dfc4dd468b787b2a46ed8be846f29eac844d6d80710688c6 + checksum: 10/c3a15164065e33509b3dee05e70865e68b7b061804e70f864d6afdc55b30663ce6fed2730d9508a44672757622a454c2a38b696c262bdd0ebcbb39f28acee621 languageName: node linkType: hard @@ -43860,7 +44142,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:3, readable-stream@npm:^3.0.0, readable-stream@npm:^3.0.2, readable-stream@npm:^3.0.6, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.5.0, readable-stream@npm:^3.6.0": +"readable-stream@npm:3, readable-stream@npm:^3.0.0, readable-stream@npm:^3.0.2, readable-stream@npm:^3.0.6, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.5.0, readable-stream@npm:^3.6.0, readable-stream@npm:^3.6.2": version: 3.6.2 resolution: "readable-stream@npm:3.6.2" dependencies: @@ -43886,7 +44168,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^4.0.0, readable-stream@npm:^4.3.0, readable-stream@npm:^4.5.2": +"readable-stream@npm:^4.0.0, readable-stream@npm:^4.3.0": version: 4.5.2 resolution: "readable-stream@npm:4.5.2" dependencies: @@ -43938,6 +44220,13 @@ __metadata: languageName: node linkType: hard +"readdirp@npm:^4.0.1": + version: 4.1.2 + resolution: "readdirp@npm:4.1.2" + checksum: 10/7b817c265940dba90bb9c94d82920d76c3a35ea2d67f9f9d8bd936adcfe02d50c802b14be3dd2e725e002dddbe2cc1c7a0edfb1bc3a365c9dfd5a61e612eea1e + languageName: node + linkType: hard + "readline-sync@npm:1.4.9": version: 1.4.9 resolution: "readline-sync@npm:1.4.9" @@ -44082,6 +44371,18 @@ __metadata: languageName: node linkType: hard +"refractor@npm:^5.0.0": + version: 5.0.0 + resolution: "refractor@npm:5.0.0" + dependencies: + "@types/hast": "npm:^3.0.0" + "@types/prismjs": "npm:^1.0.0" + hastscript: "npm:^9.0.0" + parse-entities: "npm:^4.0.0" + checksum: 10/a14ceb938ed6f7f17dfa44660d3f3aff33e8896710bd76af0b411778791918bf20f923a191ff197fbc6a8e3f06c4371b83882f46ce76736f804dbe2b82b0be94 + languageName: node + linkType: hard + "regenerator-runtime@npm:^0.10.5": version: 0.10.5 resolution: "regenerator-runtime@npm:0.10.5" @@ -44096,7 +44397,7 @@ __metadata: languageName: node linkType: hard -"regexp.prototype.flags@npm:^1.5.3": +"regexp.prototype.flags@npm:^1.5.3, regexp.prototype.flags@npm:^1.5.4": version: 1.5.4 resolution: "regexp.prototype.flags@npm:1.5.4" dependencies: @@ -44243,14 +44544,13 @@ __metadata: languageName: node linkType: hard -"require-in-the-middle@npm:^7.1.1": - version: 7.3.0 - resolution: "require-in-the-middle@npm:7.3.0" +"require-in-the-middle@npm:^8.0.0": + version: 8.0.1 + resolution: "require-in-the-middle@npm:8.0.1" dependencies: - debug: "npm:^4.1.1" + debug: "npm:^4.3.5" module-details-from-path: "npm:^1.0.3" - resolve: "npm:^1.22.1" - checksum: 10/883343b9ba15d42dd443b20fba5f9135cc4b7c2c2af3ae87f0105c28c499f98438a414e49bbfafd3f607dbe60a91c0da3610d31c9c3f61d222e565a8e8dd161e + checksum: 10/4ce98c681489d383a0ffccb79b06df7a1dffbb31c13f3b713ae2c5a1967597a259e67612507ef69748d83d531bba7c9bb0477211771fe78c685e1d52b1a44b64 languageName: node linkType: hard @@ -44349,13 +44649,6 @@ __metadata: languageName: node linkType: hard -"resolve.exports@npm:^2.0.0": - version: 2.0.0 - resolution: "resolve.exports@npm:2.0.0" - checksum: 10/a94fe2c064c57b4cc64de8c13eab28f744ee2327af4e458f2db00b5bbc3e7bf91b59fca043e35e2a3cf70bdae2263825c926b62200e2eb78d5c55a88d14932a8 - languageName: node - linkType: hard - "resolve@npm:1.22.8": version: 1.22.8 resolution: "resolve@npm:1.22.8" @@ -44635,33 +44928,33 @@ __metadata: linkType: hard "rollup-plugin-dts@npm:^6.1.0": - version: 6.1.1 - resolution: "rollup-plugin-dts@npm:6.1.1" + version: 6.3.0 + resolution: "rollup-plugin-dts@npm:6.3.0" dependencies: - "@babel/code-frame": "npm:^7.24.2" - magic-string: "npm:^0.30.10" + "@babel/code-frame": "npm:^7.27.1" + magic-string: "npm:^0.30.21" peerDependencies: rollup: ^3.29.4 || ^4 typescript: ^4.5 || ^5.0 dependenciesMeta: "@babel/code-frame": optional: true - checksum: 10/8a66833a5af32f77d9bbc746339097d4af2382e5160f7629d85dcecb4efad12cbfebd37c79147fa688f073c333d71f53135e08a225a3fc3e9a3b3f92c46b2381 + checksum: 10/1b2b25126eee0c4e7f59d82ee5850b0c5779a1a0bb77d677798e20a3d29dba76d1a19a73300ed5b20aab02f6888ecc4209a95b07f6cc9c1b80af14bdcbeda5b9 languageName: node linkType: hard "rollup-plugin-esbuild@npm:^6.1.1": - version: 6.1.1 - resolution: "rollup-plugin-esbuild@npm:6.1.1" + version: 6.2.1 + resolution: "rollup-plugin-esbuild@npm:6.2.1" dependencies: - "@rollup/pluginutils": "npm:^5.0.5" - debug: "npm:^4.3.4" - es-module-lexer: "npm:^1.3.1" - get-tsconfig: "npm:^4.7.2" + debug: "npm:^4.4.0" + es-module-lexer: "npm:^1.6.0" + get-tsconfig: "npm:^4.10.0" + unplugin-utils: "npm:^0.2.4" peerDependencies: esbuild: ">=0.18.0" rollup: ^1.20.0 || ^2.0.0 || ^3.0.0 || ^4.0.0 - checksum: 10/bba2d1dfb92a193823ac9dd1cdd44a8fd8cd9f25868e9a22ca077e1b7445feb4eaaf6df051148e367fc902d7d59c9f50efab49086c24c367972f05c86f3a656d + checksum: 10/e5731b86c4e01c6ccd3998a2f90794bd738fe83a3a809b9e82456e4b1d173c296ef302c2a6669b966b12c757c1787f0585ae88cd73b98f3c0ad6ad7f779940aa languageName: node linkType: hard @@ -44725,29 +45018,31 @@ __metadata: linkType: hard "rollup@npm:^4.27.3, rollup@npm:^4.43.0": - version: 4.46.2 - resolution: "rollup@npm:4.46.2" + version: 4.53.3 + resolution: "rollup@npm:4.53.3" dependencies: - "@rollup/rollup-android-arm-eabi": "npm:4.46.2" - "@rollup/rollup-android-arm64": "npm:4.46.2" - "@rollup/rollup-darwin-arm64": "npm:4.46.2" - "@rollup/rollup-darwin-x64": "npm:4.46.2" - "@rollup/rollup-freebsd-arm64": "npm:4.46.2" - "@rollup/rollup-freebsd-x64": "npm:4.46.2" - "@rollup/rollup-linux-arm-gnueabihf": "npm:4.46.2" - "@rollup/rollup-linux-arm-musleabihf": "npm:4.46.2" - "@rollup/rollup-linux-arm64-gnu": "npm:4.46.2" - "@rollup/rollup-linux-arm64-musl": "npm:4.46.2" - "@rollup/rollup-linux-loongarch64-gnu": "npm:4.46.2" - "@rollup/rollup-linux-ppc64-gnu": "npm:4.46.2" - "@rollup/rollup-linux-riscv64-gnu": "npm:4.46.2" - "@rollup/rollup-linux-riscv64-musl": "npm:4.46.2" - "@rollup/rollup-linux-s390x-gnu": "npm:4.46.2" - "@rollup/rollup-linux-x64-gnu": "npm:4.46.2" - "@rollup/rollup-linux-x64-musl": "npm:4.46.2" - "@rollup/rollup-win32-arm64-msvc": "npm:4.46.2" - "@rollup/rollup-win32-ia32-msvc": "npm:4.46.2" - "@rollup/rollup-win32-x64-msvc": "npm:4.46.2" + "@rollup/rollup-android-arm-eabi": "npm:4.53.3" + "@rollup/rollup-android-arm64": "npm:4.53.3" + "@rollup/rollup-darwin-arm64": "npm:4.53.3" + "@rollup/rollup-darwin-x64": "npm:4.53.3" + "@rollup/rollup-freebsd-arm64": "npm:4.53.3" + "@rollup/rollup-freebsd-x64": "npm:4.53.3" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.53.3" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.53.3" + "@rollup/rollup-linux-arm64-gnu": "npm:4.53.3" + "@rollup/rollup-linux-arm64-musl": "npm:4.53.3" + "@rollup/rollup-linux-loong64-gnu": "npm:4.53.3" + "@rollup/rollup-linux-ppc64-gnu": "npm:4.53.3" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.53.3" + "@rollup/rollup-linux-riscv64-musl": "npm:4.53.3" + "@rollup/rollup-linux-s390x-gnu": "npm:4.53.3" + "@rollup/rollup-linux-x64-gnu": "npm:4.53.3" + "@rollup/rollup-linux-x64-musl": "npm:4.53.3" + "@rollup/rollup-openharmony-arm64": "npm:4.53.3" + "@rollup/rollup-win32-arm64-msvc": "npm:4.53.3" + "@rollup/rollup-win32-ia32-msvc": "npm:4.53.3" + "@rollup/rollup-win32-x64-gnu": "npm:4.53.3" + "@rollup/rollup-win32-x64-msvc": "npm:4.53.3" "@types/estree": "npm:1.0.8" fsevents: "npm:~2.3.2" dependenciesMeta: @@ -44771,7 +45066,7 @@ __metadata: optional: true "@rollup/rollup-linux-arm64-musl": optional: true - "@rollup/rollup-linux-loongarch64-gnu": + "@rollup/rollup-linux-loong64-gnu": optional: true "@rollup/rollup-linux-ppc64-gnu": optional: true @@ -44785,17 +45080,21 @@ __metadata: optional: true "@rollup/rollup-linux-x64-musl": optional: true + "@rollup/rollup-openharmony-arm64": + optional: true "@rollup/rollup-win32-arm64-msvc": optional: true "@rollup/rollup-win32-ia32-msvc": optional: true + "@rollup/rollup-win32-x64-gnu": + optional: true "@rollup/rollup-win32-x64-msvc": optional: true fsevents: optional: true bin: rollup: dist/bin/rollup - checksum: 10/3acc425a9828e8ba75eaec9cb506a680d3deaa09a753635140cc4a3c98445ba6e3f631b32ca1c98965b3f60672d2815cd560ea844bba497d69e65c58c02327cf + checksum: 10/e2eff82405061fa907f15dfbf742b1f5fb4b214495c00989bcdbe21da5fcb3f6dec3deabacec491300a53c99da409586cfc77bdf29b411fccb9089b72cd3728d languageName: node linkType: hard @@ -44815,16 +45114,17 @@ __metadata: "@octokit/rest": "npm:^19.0.3" "@playwright/test": "npm:^1.32.3" "@spotify/eslint-plugin": "npm:^15.0.0" - "@storybook/addon-a11y": "npm:^9.1.7" - "@storybook/addon-docs": "npm:^9.1.7" - "@storybook/addon-links": "npm:^9.1.7" - "@storybook/addon-themes": "npm:^9.1.7" - "@storybook/react-vite": "npm:^9.1.7" + "@storybook/addon-a11y": "npm:^10.1.9" + "@storybook/addon-docs": "npm:^10.1.9" + "@storybook/addon-links": "npm:^10.1.9" + "@storybook/addon-themes": "npm:^10.1.9" + "@storybook/addon-vitest": "npm:^10.1.9" + "@storybook/react-vite": "npm:^10.1.9" "@techdocs/cli": "workspace:*" "@types/cacheable-request": "npm:^8.3.6" "@types/global-agent": "npm:^2.1.3" "@types/memjs": "npm:^1.3.3" - "@types/node": "npm:^20.16.0" + "@types/node": "npm:^22.13.14" "@types/webpack": "npm:^5.28.0" "@useoptic/optic": "npm:^1.0.0" array-to-table: "npm:^1.0.1" @@ -44837,6 +45137,7 @@ __metadata: eslint-plugin-testing-library: "npm:^6.0.0" fs-extra: "npm:^11.2.0" husky: "npm:^9.0.0" + js-yaml: "npm:^4.1.1" lint-staged: "npm:^15.0.0" madge: "npm:^8.0.0" minimist: "npm:^1.2.5" @@ -44846,7 +45147,7 @@ __metadata: shx: "npm:^0.4.0" sloc: "npm:^0.3.1" sort-package-json: "npm:^2.8.0" - storybook: "npm:^9.1.7" + storybook: "npm:^10.1.10" ts-morph: "npm:^24.0.0" typedoc: "npm:^0.28.0" typescript: "npm:~5.7.0" @@ -44875,6 +45176,13 @@ __metadata: languageName: node linkType: hard +"rrweb-cssom@npm:^0.8.0": + version: 0.8.0 + resolution: "rrweb-cssom@npm:0.8.0" + checksum: 10/07521ee36fb6569c17906afad1ac7ff8f099d49ade9249e190693ac36cdf27f88d9acf0cc66978935d5d0a23fca105643d7e9125b9a9d91ed9db9e02d31d7d80 + languageName: node + linkType: hard + "rtl-css-js@npm:^1.16.1": version: 1.16.1 resolution: "rtl-css-js@npm:1.16.1" @@ -44900,7 +45208,7 @@ __metadata: languageName: node linkType: hard -"run-parallel@npm:^1.1.9, run-parallel@npm:^1.2.0": +"run-parallel@npm:^1.1.9": version: 1.2.0 resolution: "run-parallel@npm:1.2.0" dependencies: @@ -44968,6 +45276,16 @@ __metadata: languageName: node linkType: hard +"safe-push-apply@npm:^1.0.0": + version: 1.0.0 + resolution: "safe-push-apply@npm:1.0.0" + dependencies: + es-errors: "npm:^1.3.0" + isarray: "npm:^2.0.5" + checksum: 10/2bd4e53b6694f7134b9cf93631480e7fafc8637165f0ee91d5a4af5e7f33d37de9562d1af5021178dd4217d0230cde8d6530fa28cfa1ebff9a431bf8fff124b4 + languageName: node + linkType: hard + "safe-regex-test@npm:^1.0.3, safe-regex-test@npm:^1.1.0": version: 1.1.0 resolution: "safe-regex-test@npm:1.1.0" @@ -45086,7 +45404,7 @@ __metadata: languageName: node linkType: hard -"schema-utils@npm:^3.0.0, schema-utils@npm:^3.1.1, schema-utils@npm:^3.2.0": +"schema-utils@npm:^3.0.0, schema-utils@npm:^3.1.1": version: 3.3.0 resolution: "schema-utils@npm:3.3.0" dependencies: @@ -45097,15 +45415,15 @@ __metadata: languageName: node linkType: hard -"schema-utils@npm:^4.0.0, schema-utils@npm:^4.2.0, schema-utils@npm:^4.3.0, schema-utils@npm:^4.3.2": - version: 4.3.2 - resolution: "schema-utils@npm:4.3.2" +"schema-utils@npm:^4.0.0, schema-utils@npm:^4.2.0, schema-utils@npm:^4.3.0, schema-utils@npm:^4.3.3": + version: 4.3.3 + resolution: "schema-utils@npm:4.3.3" dependencies: "@types/json-schema": "npm:^7.0.9" ajv: "npm:^8.9.0" ajv-formats: "npm:^2.1.1" ajv-keywords: "npm:^5.1.0" - checksum: 10/02c32c34aae762d48468f98465a96a167fede637772871c7c7d8923671ddb9f20b2cc6f6e8448ae6bef5363e3597493c655212c8b06a4ee73aa099d9452fbd8b + checksum: 10/dba77a46ad7ff0c906f7f09a1a61109e6cb56388f15a68070b93c47a691f516c6a3eb454f81a8cceb0a0e55b87f8b05770a02bfb1f4e0a3143b5887488b2f900 languageName: node linkType: hard @@ -45224,7 +45542,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.1.2, semver@npm:^7.1.3, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.2": +"semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.1.2, semver@npm:^7.1.3, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.2, semver@npm:^7.7.2": version: 7.7.3 resolution: "semver@npm:7.7.3" bin: @@ -45273,6 +45591,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 + "seq-queue@npm:^0.0.5": version: 0.0.5 resolution: "seq-queue@npm:0.0.5" @@ -45337,18 +45676,6 @@ __metadata: languageName: node linkType: hard -"serve-static@npm:1.16.2": - version: 1.16.2 - resolution: "serve-static@npm:1.16.2" - dependencies: - encodeurl: "npm:~2.0.0" - escape-html: "npm:~1.0.3" - parseurl: "npm:~1.3.3" - send: "npm:0.19.0" - checksum: 10/7fa9d9c68090f6289976b34fc13c50ac8cd7f16ae6bce08d16459300f7fc61fbc2d7ebfa02884c073ec9d6ab9e7e704c89561882bbe338e99fcacb2912fde737 - languageName: node - linkType: hard - "serve-static@npm:^2.2.0": version: 2.2.0 resolution: "serve-static@npm:2.2.0" @@ -45361,6 +45688,18 @@ __metadata: languageName: node linkType: hard +"serve-static@npm:~1.16.2": + version: 1.16.2 + resolution: "serve-static@npm:1.16.2" + dependencies: + encodeurl: "npm:~2.0.0" + escape-html: "npm:~1.0.3" + parseurl: "npm:~1.3.3" + send: "npm:0.19.0" + checksum: 10/7fa9d9c68090f6289976b34fc13c50ac8cd7f16ae6bce08d16459300f7fc61fbc2d7ebfa02884c073ec9d6ab9e7e704c89561882bbe338e99fcacb2912fde737 + languageName: node + linkType: hard + "set-blocking@npm:^2.0.0": version: 2.0.0 resolution: "set-blocking@npm:2.0.0" @@ -45408,6 +45747,17 @@ __metadata: languageName: node linkType: hard +"set-proto@npm:^1.0.0": + version: 1.0.0 + resolution: "set-proto@npm:1.0.0" + dependencies: + dunder-proto: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + checksum: 10/b87f8187bca595ddc3c0721ece4635015fd9d7cb294e6dd2e394ce5186a71bbfa4dc8a35010958c65e43ad83cde09642660e61a952883c24fd6b45ead15f045c + languageName: node + linkType: hard + "set-value@npm:^4.1.0": version: 4.1.0 resolution: "set-value@npm:4.1.0" @@ -45432,14 +45782,14 @@ __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 languageName: node linkType: hard -"sha.js@npm:^2.4.0, sha.js@npm:^2.4.11, sha.js@npm:^2.4.12, sha.js@npm:^2.4.8, sha.js@npm:^2.4.9": +"sha.js@npm:^2.4.0, sha.js@npm:^2.4.11, sha.js@npm:^2.4.12, sha.js@npm:^2.4.8": version: 2.4.12 resolution: "sha.js@npm:2.4.12" dependencies: @@ -45452,6 +45802,15 @@ __metadata: languageName: node linkType: hard +"shadow-dom-testing-library@npm:^1.13.1": + version: 1.13.1 + resolution: "shadow-dom-testing-library@npm:1.13.1" + peerDependencies: + "@testing-library/dom": ">= 8" + checksum: 10/a10f3466592691368f260c15230d9e830cc5122d20002ad9b6c2918fa245089219d1f45c026153fa6ccf8848b546da6f985c0e461458c8c1e09786a1de626287 + languageName: node + linkType: hard + "shallow-clone@npm:^3.0.0": version: 3.0.1 resolution: "shallow-clone@npm:3.0.1" @@ -45521,13 +45880,6 @@ __metadata: languageName: node linkType: hard -"shimmer@npm:^1.2.1": - version: 1.2.1 - resolution: "shimmer@npm:1.2.1" - checksum: 10/aa0d6252ad1c682a4fdfda69e541be987f7a265ac7b00b1208e5e48cc68dc55f293955346ea4c71a169b7324b82c70f8400b3d3d2d60b2a7519f0a3522423250 - languageName: node - linkType: hard - "short-unique-id@npm:^5.3.2": version: 5.3.2 resolution: "short-unique-id@npm:5.3.2" @@ -45585,7 +45937,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: @@ -45653,15 +46005,6 @@ __metadata: languageName: node linkType: hard -"simple-swizzle@npm:^0.2.2": - version: 0.2.2 - resolution: "simple-swizzle@npm:0.2.2" - dependencies: - is-arrayish: "npm:^0.3.1" - checksum: 10/c6dffff17aaa383dae7e5c056fbf10cf9855a9f79949f20ee225c04f06ddde56323600e0f3d6797e82d08d006e93761122527438ee9531620031c08c9e0d73cc - languageName: node - linkType: hard - "simple-update-notifier@npm:^2.0.0": version: 2.0.0 resolution: "simple-update-notifier@npm:2.0.0" @@ -45769,10 +46112,10 @@ __metadata: languageName: node linkType: hard -"smol-toml@npm:^1.3.1": - version: 1.3.1 - resolution: "smol-toml@npm:1.3.1" - checksum: 10/b999828ea46cf44ae90b6293884d6a139dfb4545ac6f86cbd1002568a943a43d8895ad82413855d095eec0c0bc21d23413c0a25a26c7fad6395c2ce42c2fdbd0 +"smol-toml@npm:^1.5.2": + version: 1.5.2 + resolution: "smol-toml@npm:1.5.2" + checksum: 10/0a7e9192d1cbd04c3122224306de33962f4f5ac7e78a48b7c3795a675683b6efeb8f73ad3fabda7635926a510948cb4654a729d17b35ec43d56d35a71cf906f4 languageName: node linkType: hard @@ -45928,7 +46271,7 @@ __metadata: languageName: node linkType: hard -"source-map-js@npm:^1.2.1": +"source-map-js@npm:^1.0.1, source-map-js@npm:^1.2.1": version: 1.2.1 resolution: "source-map-js@npm:1.2.1" checksum: 10/ff9d8c8bf096d534a5b7707e0382ef827b4dd360a577d3f34d2b9f48e12c9d230b5747974ee7c607f0df65113732711bb701fe9ece3c7edbd43cb2294d707df3 @@ -46057,12 +46400,10 @@ __metadata: languageName: node linkType: hard -"split@npm:^1.0.0": - version: 1.0.1 - resolution: "split@npm:1.0.1" - dependencies: - through: "npm:2" - checksum: 10/12f4554a5792c7e98bb3e22b53c63bfa5ef89aa704353e1db608a55b51f5b12afaad6e4a8ecf7843c15f273f43cdadd67b3705cc43d48a75c2cf4641d51f7e7a +"split2@npm:^4.1.0": + version: 4.2.0 + resolution: "split2@npm:4.2.0" + checksum: 10/09bbefc11bcf03f044584c9764cd31a252d8e52cea29130950b26161287c11f519807c5e54bd9e5804c713b79c02cefe6a98f4688630993386be353e03f534ab languageName: node linkType: hard @@ -46182,7 +46523,7 @@ __metadata: languageName: node linkType: hard -"stack-utils@npm:^2.0.2, stack-utils@npm:^2.0.3": +"stack-utils@npm:^2.0.2, stack-utils@npm:^2.0.6": version: 2.0.6 resolution: "stack-utils@npm:2.0.6" dependencies: @@ -46235,7 +46576,7 @@ __metadata: languageName: node linkType: hard -"statuses@npm:2.0.1, statuses@npm:^2.0.1": +"statuses@npm:2.0.1": version: 2.0.1 resolution: "statuses@npm:2.0.1" checksum: 10/18c7623fdb8f646fb213ca4051be4df7efb3484d4ab662937ca6fbef7ced9b9e12842709872eb3020cc3504b93bde88935c9f6417489627a7786f24f8031cbcb @@ -46249,7 +46590,14 @@ __metadata: languageName: node linkType: hard -"stop-iteration-iterator@npm:^1.0.0": +"statuses@npm:^2.0.1, 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 + +"stop-iteration-iterator@npm:^1.0.0, stop-iteration-iterator@npm:^1.1.0": version: 1.1.0 resolution: "stop-iteration-iterator@npm:1.1.0" dependencies: @@ -46266,21 +46614,21 @@ __metadata: languageName: node linkType: hard -"storybook@npm:^9.1.7": - version: 9.1.16 - resolution: "storybook@npm:9.1.16" +"storybook@npm:^10.1.10": + version: 10.1.10 + resolution: "storybook@npm:10.1.10" dependencies: "@storybook/global": "npm:^5.0.0" + "@storybook/icons": "npm:^2.0.0" "@testing-library/jest-dom": "npm:^6.6.3" "@testing-library/user-event": "npm:^14.6.1" "@vitest/expect": "npm:3.2.4" - "@vitest/mocker": "npm:3.2.4" "@vitest/spy": "npm:3.2.4" - better-opn: "npm:^3.0.2" - esbuild: "npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0" - esbuild-register: "npm:^3.5.0" + esbuild: "npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0" + open: "npm:^10.2.0" recast: "npm:^0.23.5" semver: "npm:^7.6.2" + use-sync-external-store: "npm:^1.5.0" ws: "npm:^8.18.0" peerDependencies: prettier: ^2 || ^3 @@ -46288,8 +46636,8 @@ __metadata: prettier: optional: true bin: - storybook: ./bin/index.cjs - checksum: 10/62a79c47bd0ac65af8d2de4123332a578aaff76e94ffba45293633db0c316488d7b603ffe47b23f998a872e6428c25b9fd944bc8eb203318394f421490021e96 + storybook: ./dist/bin/dispatcher.js + checksum: 10/c1f01c7ab57e80d2f2ef3a5c49baad5904e77c8e079199ad134e98a7ae455d52422390cd704e64142b36668874c055670dbffe0e334e1f4d541ebd4384052dd7 languageName: node linkType: hard @@ -46454,13 +46802,13 @@ __metadata: languageName: node linkType: hard -"string-length@npm:^4.0.1": - version: 4.0.1 - resolution: "string-length@npm:4.0.1" +"string-length@npm:^4.0.2": + version: 4.0.2 + resolution: "string-length@npm:4.0.2" dependencies: char-regex: "npm:^1.0.2" strip-ansi: "npm:^6.0.0" - checksum: 10/7bd3191668ddafa6f574a8b17a1bd1b085737d64ceefa51f72cdd19c45a730422cd70d984eee7584d6e5b5c84b6318633c6d6a720a4bfd7c58769985fa77573e + checksum: 10/ce85533ef5113fcb7e522bcf9e62cb33871aa99b3729cec5595f4447f660b0cefd542ca6df4150c97a677d58b0cb727a3fe09ac1de94071d05526c73579bf505 languageName: node linkType: hard @@ -46554,7 +46902,7 @@ __metadata: languageName: node linkType: hard -"string.prototype.trimend@npm:^1.0.8, string.prototype.trimend@npm:^1.0.9": +"string.prototype.trimend@npm:^1.0.9": version: 1.0.9 resolution: "string.prototype.trimend@npm:1.0.9" dependencies: @@ -46714,10 +47062,10 @@ __metadata: languageName: node linkType: hard -"strip-json-comments@npm:5.0.1": - version: 5.0.1 - resolution: "strip-json-comments@npm:5.0.1" - checksum: 10/b314af70c6666a71133e309a571bdb87687fc878d9fd8b38ebed393a77b89835b92f191aa6b0bc10dfd028ba99eed6b6365985001d64c5aef32a4a82456a156b +"strip-json-comments@npm:5.0.3": + version: 5.0.3 + resolution: "strip-json-comments@npm:5.0.3" + checksum: 10/3ccbf26f278220f785e4b71f8a719a6a063d72558cc63cb450924254af258a4f4c008b8c9b055373a680dc7bd525be9e543ad742c177f8a7667e0b726258e0e4 languageName: node linkType: hard @@ -46877,27 +47225,20 @@ __metadata: linkType: hard "sucrase@npm:^3.20.2": - version: 3.35.0 - resolution: "sucrase@npm:3.35.0" + version: 3.35.1 + resolution: "sucrase@npm:3.35.1" dependencies: "@jridgewell/gen-mapping": "npm:^0.3.2" commander: "npm:^4.0.0" - glob: "npm:^10.3.10" lines-and-columns: "npm:^1.1.6" mz: "npm:^2.7.0" pirates: "npm:^4.0.1" + tinyglobby: "npm:^0.2.11" ts-interface-checker: "npm:^0.1.9" bin: sucrase: bin/sucrase sucrase-node: bin/sucrase-node - checksum: 10/bc601558a62826f1c32287d4fdfa4f2c09fe0fec4c4d39d0e257fd9116d7d6227a18309721d4185ec84c9dc1af0d5ec0e05a42a337fbb74fc293e068549aacbe - languageName: node - linkType: hard - -"summary@npm:2.1.0": - version: 2.1.0 - resolution: "summary@npm:2.1.0" - checksum: 10/10ac12ce12c013b56ad44c37cfac206961f0993d98867b33b1b03a27b38a1cf8dd2db0b788883356c5335bbbb37d953772ef4a381d6fc8f408faf99f2bc54af5 + checksum: 10/539f5c6ebc1ff8d449a89eb52b8c8944a730b9840ddadbd299a7d89ebcf16c3f4bc9aa59e1f2e112a502e5cf1508f7e02065f0e97c0435eb9a7058e997dfff5a languageName: node linkType: hard @@ -46988,17 +47329,17 @@ __metadata: languageName: node linkType: hard -"swagger-client@npm:^3.35.5": - version: 3.35.6 - resolution: "swagger-client@npm:3.35.6" +"swagger-client@npm:^3.36.0": + version: 3.36.0 + resolution: "swagger-client@npm:3.36.0" dependencies: "@babel/runtime-corejs3": "npm:^7.22.15" "@scarf/scarf": "npm:=1.4.0" - "@swagger-api/apidom-core": "npm:>=1.0.0-beta.41 <1.0.0-rc.0" - "@swagger-api/apidom-error": "npm:>=1.0.0-beta.41 <1.0.0-rc.0" - "@swagger-api/apidom-json-pointer": "npm:>=1.0.0-beta.41 <1.0.0-rc.0" - "@swagger-api/apidom-ns-openapi-3-1": "npm:>=1.0.0-beta.41 <1.0.0-rc.0" - "@swagger-api/apidom-reference": "npm:>=1.0.0-beta.41 <1.0.0-rc.0" + "@swagger-api/apidom-core": "npm:^1.0.0-rc.1" + "@swagger-api/apidom-error": "npm:^1.0.0-rc.1" + "@swagger-api/apidom-json-pointer": "npm:^1.0.0-rc.1" + "@swagger-api/apidom-ns-openapi-3-1": "npm:^1.0.0-rc.1" + "@swagger-api/apidom-reference": "npm:^1.0.0-rc.1" "@swaggerexpert/cookie": "npm:^2.0.2" deepmerge: "npm:~4.3.0" fast-json-patch: "npm:^3.0.0-1" @@ -47010,13 +47351,13 @@ __metadata: openapi-server-url-templating: "npm:^1.3.0" ramda: "npm:^0.30.1" ramda-adjunct: "npm:^5.1.0" - checksum: 10/fb6582dc45c2ae6a26ae4cc80f0a3d97e31f3b058676951f93670828f2f30f5d44ecd7c029465f5452b251a533be329a955c92cec11be7128baa5e2e963e6f9d + checksum: 10/ea69e017cf266b44e2ef8e01a862975789bc62a537ab28baffb80b51d99640e35fac44eea5b744e98d0f6151fa290a09f840a5ad3f0103ead508e628b10dbc7b languageName: node linkType: hard "swagger-ui-react@npm:^5.27.1": - version: 5.29.0 - resolution: "swagger-ui-react@npm:5.29.0" + version: 5.30.3 + resolution: "swagger-ui-react@npm:5.30.3" dependencies: "@babel/runtime-corejs3": "npm:^7.27.1" "@scarf/scarf": "npm:=1.4.0" @@ -47025,11 +47366,11 @@ __metadata: classnames: "npm:^2.5.1" css.escape: "npm:1.5.1" deep-extend: "npm:0.6.0" - dompurify: "npm:=3.2.4" + dompurify: "npm:=3.2.6" ieee754: "npm:^1.2.1" immutable: "npm:^3.x.x" js-file-download: "npm:^0.4.12" - js-yaml: "npm:=4.1.0" + js-yaml: "npm:=4.1.1" lodash: "npm:^4.17.21" prop-types: "npm:^15.8.1" randexp: "npm:^0.5.3" @@ -47040,14 +47381,14 @@ __metadata: react-immutable-pure-component: "npm:^2.2.0" react-inspector: "npm:^6.0.1" react-redux: "npm:^9.2.0" - react-syntax-highlighter: "npm:^15.6.1" + react-syntax-highlighter: "npm:^16.0.0" redux: "npm:^5.0.1" redux-immutable: "npm:^4.0.0" remarkable: "npm:^2.0.1" reselect: "npm:^5.1.1" serialize-error: "npm:^8.1.0" sha.js: "npm:^2.4.12" - swagger-client: "npm:^3.35.5" + swagger-client: "npm:^3.36.0" url-parse: "npm:^1.5.10" xml: "npm:=1.0.1" xml-but-prettier: "npm:^1.0.1" @@ -47055,7 +47396,7 @@ __metadata: peerDependencies: react: ">=16.8.0 <20" react-dom: ">=16.8.0 <20" - checksum: 10/994a7b06b17fe12693a7a09057161723f5e2ab2c80c747879951a54f0d7a0cd4c65c139bdaf229f13d61f5dd61c2b63218881fc23081ced0cc91745b48d806d5 + checksum: 10/6f466414072806e60a3e36d58029b0679cd80cd9488e79a2142bebcd0829efa587f1b3249c2feab3ab692b3a70f5b90038f3fd14430f8ae41c6ec904f8041f79 languageName: node linkType: hard @@ -47072,14 +47413,14 @@ __metadata: linkType: hard "swr@npm:^2.0.0, swr@npm:^2.2.5": - version: 2.2.5 - resolution: "swr@npm:2.2.5" + version: 2.3.7 + resolution: "swr@npm:2.3.7" dependencies: - client-only: "npm:^0.0.1" - use-sync-external-store: "npm:^1.2.0" + dequal: "npm:^2.0.3" + use-sync-external-store: "npm:^1.4.0" peerDependencies: - react: ^16.11.0 || ^17.0.0 || ^18.0.0 - checksum: 10/f02b3bd5a198a0f62f9a53d7c0528c4a58aa61a43310bea169614b6e873dadb52599e856ef0775405b6aa7409835343da0cf328948aa892aa309bf4b7e7d6902 + react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10/a7a9f141ea31146f2787c36854ca0998f65d38503fe73fc3c0695ebba37821487eb43e62cece15d1739bc531e8430c15ac5f6ea695293e861fdbc2de92514d96 languageName: node linkType: hard @@ -47097,6 +47438,15 @@ __metadata: languageName: node linkType: hard +"synckit@npm:^0.11.8": + version: 0.11.11 + resolution: "synckit@npm:0.11.11" + dependencies: + "@pkgr/core": "npm:^0.2.9" + checksum: 10/6ecd88212b5be80004376b6ea74babcba284566ff59a50d8803afcaa78c165b5d268635c1dd84532ee3f690a979409e1eda225a8a35bed2d135ffdcea06ce7b0 + languageName: node + linkType: hard + "tapable@npm:^1.0.0": version: 1.1.3 resolution: "tapable@npm:1.1.3" @@ -47104,10 +47454,10 @@ __metadata: languageName: node linkType: hard -"tapable@npm:^2.0.0, tapable@npm:^2.1.1, tapable@npm:^2.2.0, tapable@npm:^2.2.1": - version: 2.2.1 - resolution: "tapable@npm:2.2.1" - checksum: 10/1769336dd21481ae6347611ca5fca47add0962fd8e80466515032125eca0084a4f0ede11e65341b9c0018ef4e1cf1ad820adbb0fba7cc99865c6005734000b0a +"tapable@npm:^2.0.0, tapable@npm:^2.2.0, tapable@npm:^2.2.1, tapable@npm:^2.3.0": + version: 2.3.0 + resolution: "tapable@npm:2.3.0" + checksum: 10/496a841039960533bb6e44816a01fffc2a1eb428bb2051ecab9e87adf07f19e1f937566cbbbb09dceff31163c0ffd81baafcad84db900b601f0155dd0b37e9f2 languageName: node linkType: hard @@ -47123,7 +47473,7 @@ __metadata: languageName: node linkType: hard -"tar-fs@npm:^3.0.6, tar-fs@npm:^3.0.9": +"tar-fs@npm:^3.0.9, tar-fs@npm:^3.1.1": version: 3.1.1 resolution: "tar-fs@npm:3.1.1" dependencies: @@ -47140,19 +47490,7 @@ __metadata: languageName: node linkType: hard -"tar-fs@npm:~2.0.1": - version: 2.0.1 - resolution: "tar-fs@npm:2.0.1" - dependencies: - chownr: "npm:^1.1.1" - mkdirp-classic: "npm:^0.5.2" - pump: "npm:^3.0.0" - tar-stream: "npm:^2.0.0" - checksum: 10/85ceac6fce0e9175b5b67c0eca8864b7d29a940cae8b7657c60b66e8a252319d701c3df12814162a6839e6120f9e1975757293bdeaf294ad5b15721d236c4d32 - languageName: node - linkType: hard - -"tar-stream@npm:^2.0.0, tar-stream@npm:^2.0.1, tar-stream@npm:^2.1.4": +"tar-stream@npm:^2.0.1, tar-stream@npm:^2.1.4": version: 2.2.0 resolution: "tar-stream@npm:2.2.0" dependencies: @@ -47301,9 +47639,9 @@ __metadata: languageName: node linkType: hard -"terser-webpack-plugin@npm:*, terser-webpack-plugin@npm:^5.1.3, terser-webpack-plugin@npm:^5.3.10, terser-webpack-plugin@npm:^5.3.11": - version: 5.3.14 - resolution: "terser-webpack-plugin@npm:5.3.14" +"terser-webpack-plugin@npm:*, terser-webpack-plugin@npm:^5.1.3, terser-webpack-plugin@npm:^5.3.11": + version: 5.3.16 + resolution: "terser-webpack-plugin@npm:5.3.16" dependencies: "@jridgewell/trace-mapping": "npm:^0.3.25" jest-worker: "npm:^27.4.5" @@ -47319,7 +47657,7 @@ __metadata: optional: true uglify-js: optional: true - checksum: 10/5b7290f7edb179b83cefb8827c12371ddddc088cf251cf58a1c738d82628331ae6604273b61fe991d77411d4bb6b7178c3826aa47edf01b4ee21f973d6c8b8fb + checksum: 10/09dfbff602acfa114cdd174254b69a04adbc47856021ab351e37982202fd1ec85e0b62ffd5864c98beb8e96aef2f43da490b3448b4541db539c2cff6607394a6 languageName: node linkType: hard @@ -47348,33 +47686,26 @@ __metadata: languageName: node linkType: hard -"testcontainers@npm:^10.0.0": - version: 10.14.0 - resolution: "testcontainers@npm:10.14.0" +"testcontainers@npm:^11.9.0": + version: 11.9.0 + resolution: "testcontainers@npm:11.9.0" dependencies: "@balena/dockerignore": "npm:^1.0.2" - "@types/dockerode": "npm:^3.3.29" + "@types/dockerode": "npm:^3.3.47" archiver: "npm:^7.0.1" async-lock: "npm:^1.4.1" byline: "npm:^5.0.0" - debug: "npm:^4.3.5" - docker-compose: "npm:^0.24.8" - dockerode: "npm:^3.3.5" - get-port: "npm:^5.1.1" + debug: "npm:^4.4.3" + docker-compose: "npm:^1.3.0" + dockerode: "npm:^4.0.9" + get-port: "npm:^7.1.0" proper-lockfile: "npm:^4.1.2" properties-reader: "npm:^2.3.0" ssh-remote-port-forward: "npm:^1.0.4" - tar-fs: "npm:^3.0.6" - tmp: "npm:^0.2.3" - undici: "npm:^5.28.4" - checksum: 10/09d983b4881a6460c9167c25a0367569feefcf89f69557511c07a0d0d89be5be2e3509c0b1fe0b8c6fae80d163064eefb71d691e1a0b33465b0e8ab5c7fb5313 - languageName: node - linkType: hard - -"testing-library__dom@npm:^7.29.4-beta.1": - version: 7.29.4-beta.1 - resolution: "testing-library__dom@npm:7.29.4-beta.1" - checksum: 10/d912418803b77df672c0894d053327a4b366cf7e5312dfb73bd2089d36272e2b38bccc1bd7284d9ed2f9f6530797778d0f72e94b8a44fe5d0327d11e7f757f2b + tar-fs: "npm:^3.1.1" + tmp: "npm:^0.2.5" + undici: "npm:^7.16.0" + checksum: 10/974d729dfd2e511d8623f60629ddfb60446f6c0d0b9f41ac2db279fb94d89f6bc6bf7625734bff33c8d740886c3891cc29bda8f751edc46db6d42ca9212ba9fe languageName: node linkType: hard @@ -47387,6 +47718,13 @@ __metadata: languageName: node linkType: hard +"text-extensions@npm:^2.4.0": + version: 2.4.0 + resolution: "text-extensions@npm:2.4.0" + checksum: 10/9bdbc9959e004ccc86a6ec076d6c5bb6765978263e9d0d5febb640d7675c09919ea912f3fe9d50b68c3c7c43cc865610a7cb24954343abb31f74c205fbae4e45 + languageName: node + linkType: hard + "text-hex@npm:1.0.x": version: 1.0.0 resolution: "text-hex@npm:1.0.0" @@ -47401,7 +47739,7 @@ __metadata: languageName: node linkType: hard -"textextensions@npm:^5.12.0, textextensions@npm:^5.13.0, textextensions@npm:^5.16.0": +"textextensions@npm:^5.12.0, textextensions@npm:^5.13.0": version: 5.16.0 resolution: "textextensions@npm:5.16.0" checksum: 10/d41e9265e9d74d192d4fb26fc89a2f4dbe7a6d85cc5c14f99f1df68d07bce5346f8abe0ed680a91ef805b91e9972e5787c7365a03f3a5489e16ca350d28a3879 @@ -47451,7 +47789,7 @@ __metadata: languageName: node linkType: hard -"through@npm:2, through@npm:^2.3.6, through@npm:~2.3": +"through@npm:^2.3.6, through@npm:~2.3": version: 2.3.8 resolution: "through@npm:2.3.8" checksum: 10/5da78346f70139a7d213b65a0106f3c398d6bc5301f9248b5275f420abc2c4b1e77c2abc72d218dedc28c41efb2e7c312cb76a7730d04f9c2d37d247da3f4198 @@ -47522,7 +47860,7 @@ __metadata: languageName: node linkType: hard -"tinyglobby@npm:^0.2.15, tinyglobby@npm:^0.2.9": +"tinyglobby@npm:^0.2.11, tinyglobby@npm:^0.2.15, tinyglobby@npm:^0.2.9": version: 0.2.15 resolution: "tinyglobby@npm:0.2.15" dependencies: @@ -47553,10 +47891,10 @@ __metadata: languageName: node linkType: hard -"tldts-core@npm:^6.1.51": - version: 6.1.51 - resolution: "tldts-core@npm:6.1.51" - checksum: 10/861d5736d714abfd649c0545b9aae27058cd76f28f83077328fde6ba5ba9f7f107227d09406cfd38baf6215fc7840545ed0b17352f2f364182265887774394dc +"tldts-core@npm:^6.1.86": + version: 6.1.86 + resolution: "tldts-core@npm:6.1.86" + checksum: 10/cb5dff9cc15661ac773a2099e98c99a5cb3cebc35909c23cc4261ff7992032c7501995ae995de3574dbbf3431e59c47496534d52f5e96abcb231f0e72144c020 languageName: node linkType: hard @@ -47568,13 +47906,13 @@ __metadata: linkType: hard "tldts@npm:^6.1.32": - version: 6.1.51 - resolution: "tldts@npm:6.1.51" + version: 6.1.86 + resolution: "tldts@npm:6.1.86" dependencies: - tldts-core: "npm:^6.1.51" + tldts-core: "npm:^6.1.86" bin: tldts: bin/cli.js - checksum: 10/42e664469b1497af2fc6ac4cbf94f8a6507fcd23bf59521ace34223e36cb74ec7a003cf42834fcc4ba159c7448aa7e6f0f79f1f2fb8145b2e72ef44e87f035ec + checksum: 10/f7e66824e44479ccdda55ea556af14ce61c4d27708be403e3f90631defde49f82a580e1ca07187cc7e3b349e257a30c2808a22903f3a0548e136ebb609ccc109 languageName: node linkType: hard @@ -47589,10 +47927,10 @@ __metadata: languageName: node linkType: hard -"tmp@npm:^0.2.3": - version: 0.2.3 - resolution: "tmp@npm:0.2.3" - checksum: 10/7b13696787f159c9754793a83aa79a24f1522d47b87462ddb57c18ee93ff26c74cbb2b8d9138f571d2e0e765c728fb2739863a672b280528512c6d83d511c6fa +"tmp@npm:^0.2.5": + version: 0.2.5 + resolution: "tmp@npm:0.2.5" + checksum: 10/dd4b78b32385eab4899d3ae296007b34482b035b6d73e1201c4a9aede40860e90997a1452c65a2d21aee73d53e93cd167d741c3db4015d90e63b6d568a93d7ec languageName: node linkType: hard @@ -47637,7 +47975,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 @@ -47682,24 +48020,12 @@ __metadata: languageName: node linkType: hard -"tough-cookie@npm:^4.0.0": - version: 4.1.4 - resolution: "tough-cookie@npm:4.1.4" - dependencies: - psl: "npm:^1.1.33" - punycode: "npm:^2.1.1" - universalify: "npm:^0.2.0" - url-parse: "npm:^1.5.3" - checksum: 10/75663f4e2cd085f16af0b217e4218772adf0617fb3227171102618a54ce0187a164e505d61f773ed7d65988f8ff8a8f935d381f87da981752c1171b076b4afac - languageName: node - linkType: hard - "tough-cookie@npm:^5.0.0": - version: 5.0.0 - resolution: "tough-cookie@npm:5.0.0" + version: 5.1.2 + resolution: "tough-cookie@npm:5.1.2" dependencies: tldts: "npm:^6.1.32" - checksum: 10/a98d3846ed386e399e8b470c1eb08a6a296944246eabc55c9fe79d629bd2cdaa62f5a6572f271fe0060987906bd20468d72a219a3b4cbe51086bea48d2d677b6 + checksum: 10/de430e6e6d34b794137e05b8ac2aa6b74ebbe6cdceb4126f168cf1e76101162a4b2e0e7587c3b70e728bd8654fc39958b2035be7619ee6f08e7257610ba4cd04 languageName: node linkType: hard @@ -47712,15 +48038,6 @@ __metadata: languageName: node linkType: hard -"tr46@npm:^3.0.0": - version: 3.0.0 - resolution: "tr46@npm:3.0.0" - dependencies: - punycode: "npm:^2.1.1" - checksum: 10/b09a15886cbfaee419a3469081223489051ce9dca3374dd9500d2378adedbee84a3c73f83bfdd6bb13d53657753fc0d4e20a46bfcd3f1b9057ef528426ad7ce4 - languageName: node - linkType: hard - "tr46@npm:^5.1.0": version: 5.1.1 resolution: "tr46@npm:5.1.1" @@ -47730,6 +48047,15 @@ __metadata: languageName: node linkType: hard +"tr46@npm:^6.0.0": + version: 6.0.0 + resolution: "tr46@npm:6.0.0" + dependencies: + punycode: "npm:^2.3.1" + checksum: 10/e6d402eb2b780a40042f327f77b4ae316da1d2b18a29c16e48c239f5267c6005bbf780f854179cfae62b02dfaa70b0e9aad8f0078ccc4225f5b3b3b131928e8f + languageName: node + linkType: hard + "tr46@npm:~0.0.3": version: 0.0.3 resolution: "tr46@npm:0.0.3" @@ -47737,12 +48063,12 @@ __metadata: languageName: node linkType: hard -"tree-dump@npm:^1.0.1": - version: 1.0.3 - resolution: "tree-dump@npm:1.0.3" +"tree-dump@npm:^1.0.3, tree-dump@npm:^1.1.0": + version: 1.1.0 + resolution: "tree-dump@npm:1.1.0" peerDependencies: tslib: 2 - checksum: 10/cf382e61cfb5e3ff8f03425b5bc1923e8f0e385b3a02f43d9d0a32d09da9984477e0f2a7698628662263d1d3f1af17e33486c77ff454978f0f9f07fb5d1fe9a2 + checksum: 10/2c20118d2671996aa6f1ba1310cef1404fb525bde5d989ab542013f62b23a3633c0f0b32cbd516ee6205051ec21912b2470dabca006d19c9eba0740b567e2b60 languageName: node linkType: hard @@ -47844,7 +48170,7 @@ __metadata: languageName: node linkType: hard -"ts-api-utils@npm:^2.0.1, ts-api-utils@npm:^2.1.0": +"ts-api-utils@npm:^2.1.0": version: 2.1.0 resolution: "ts-api-utils@npm:2.1.0" peerDependencies: @@ -47854,14 +48180,14 @@ __metadata: linkType: hard "ts-checker-rspack-plugin@npm:^1.1.5": - version: 1.1.5 - resolution: "ts-checker-rspack-plugin@npm:1.1.5" + version: 1.2.1 + resolution: "ts-checker-rspack-plugin@npm:1.2.1" dependencies: "@babel/code-frame": "npm:^7.27.1" - "@rspack/lite-tapable": "npm:^1.0.1" + "@rspack/lite-tapable": "npm:^1.1.0" chokidar: "npm:^3.6.0" is-glob: "npm:^4.0.3" - memfs: "npm:^4.28.0" + memfs: "npm:^4.51.1" minimatch: "npm:^9.0.5" picocolors: "npm:^1.1.1" peerDependencies: @@ -47870,7 +48196,7 @@ __metadata: peerDependenciesMeta: "@rspack/core": optional: true - checksum: 10/d43f87a5860f3b8f528bf8f43c78c2d958a18fa484d791a204047fc5672134fd689665021ee25b07f9f9d8c807cd291978f58b789bf531b4704a9a4e3ad84735 + checksum: 10/c2e4a4e0cfda6a358e2b3c134446359f74ed57787c23cb270ae6d72bfa1fae91e42968d04c9c5921a39c2995869a56fb8f3c8f906f24d7a94a33dca31ece2f22 languageName: node linkType: hard @@ -48294,10 +48620,10 @@ __metadata: linkType: hard "typedoc@npm:^0.28.0": - version: 0.28.14 - resolution: "typedoc@npm:0.28.14" + version: 0.28.15 + resolution: "typedoc@npm:0.28.15" dependencies: - "@gerrit0/mini-shiki": "npm:^3.12.0" + "@gerrit0/mini-shiki": "npm:^3.17.0" lunr: "npm:^2.3.9" markdown-it: "npm:^14.1.0" minimatch: "npm:^9.0.5" @@ -48306,7 +48632,7 @@ __metadata: typescript: 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x bin: typedoc: bin/typedoc - checksum: 10/6b2bb87c85de96475f60ba2a92e0cb7169fef138cce4d91c949267ca9268c4fc97c2db9702f306ad5e594aa5244cc9aa563791ec31dd1b1d5288cf1ab08aa7a0 + checksum: 10/c4bfed5435c5fbd8a63f0e058b7d845c83872c2648c5db49c2ace613013dbce55ca04b30630db2c72abbbd0850d8c110781cd5da6c508e781f7a7845a0e9c0d7 languageName: node linkType: hard @@ -48319,9 +48645,9 @@ __metadata: languageName: node linkType: hard -"typescript-json-schema@npm:^0.65.0": - version: 0.65.1 - resolution: "typescript-json-schema@npm:0.65.1" +"typescript-json-schema@npm:^0.67.0": + version: 0.67.0 + resolution: "typescript-json-schema@npm:0.67.0" dependencies: "@types/json-schema": "npm:^7.0.9" "@types/node": "npm:^18.11.9" @@ -48333,17 +48659,17 @@ __metadata: yargs: "npm:^17.1.1" bin: typescript-json-schema: bin/typescript-json-schema - checksum: 10/50a1935378639d5d47e452702766a3fdab22e1d06192f26f81b79e0da504e71af987ff21cb13909479a202aad8d1216a654f16ebda2ee2056b5f859584b4c7d2 + checksum: 10/23769ab7701558aa4decb9dad832d12b9c2c619d102088023d277ce8f4abcd21453a9b095bc82de32cfca68efd1520f75124878f96dd95d8ccf91f35e1b7744f languageName: node linkType: hard -"typescript@npm:5.4.2": - version: 5.4.2 - resolution: "typescript@npm:5.4.2" +"typescript@npm:5.8.2": + version: 5.8.2 + resolution: "typescript@npm:5.8.2" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/f8cfdc630ab1672f004e9561eb2916935b2d267792d07ce93e97fc601c7a65191af32033d5e9c0169b7dc37da7db9bf320f7432bc84527cb7697effaa4e4559d + checksum: 10/dbc2168a55d56771f4d581997be52bab5cbc09734fec976cfbaabd787e61fb4c6cf9125fd48c6f98054ce549c77ecedefc7f64252a830dd8e9c3381f61fbeb78 languageName: node linkType: hard @@ -48377,13 +48703,13 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A5.4.2#optional!builtin": - version: 5.4.2 - resolution: "typescript@patch:typescript@npm%3A5.4.2#optional!builtin::version=5.4.2&hash=5adc0c" +"typescript@patch:typescript@npm%3A5.8.2#optional!builtin": + version: 5.8.2 + resolution: "typescript@patch:typescript@npm%3A5.8.2#optional!builtin::version=5.8.2&hash=5786d5" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/f5f9a4133c2670761f0166eae5b3bafbc4a3fc24f0f42a93c9c893d9e9d6e66ea066969c5e7483fa66b4ae0e99125592553f3b92fd3599484de8be13b0615176 + checksum: 10/97920a082ffc57583b1cb6bc4faa502acc156358e03f54c7fc7fdf0b61c439a717f4c9070c449ee9ee683d4cfc3bb203127c2b9794b2950f66d9d307a4ff262c languageName: node linkType: hard @@ -48526,10 +48852,10 @@ __metadata: languageName: node linkType: hard -"undici-types@npm:~6.19.2": - version: 6.19.8 - resolution: "undici-types@npm:6.19.8" - checksum: 10/cf0b48ed4fc99baf56584afa91aaffa5010c268b8842f62e02f752df209e3dea138b372a60a963b3b2576ed932f32329ce7ddb9cb5f27a6c83040d8cd74b7a70 +"undici-types@npm:~6.21.0": + version: 6.21.0 + resolution: "undici-types@npm:6.21.0" + checksum: 10/ec8f41aa4359d50f9b59fa61fe3efce3477cc681908c8f84354d8567bb3701fafdddf36ef6bff307024d3feb42c837cf6f670314ba37fc8145e219560e473d14 languageName: node linkType: hard @@ -48540,7 +48866,7 @@ __metadata: languageName: node linkType: hard -"undici@npm:^5.28.2, undici@npm:^5.28.4": +"undici@npm:^5.28.2": version: 5.29.0 resolution: "undici@npm:5.29.0" dependencies: @@ -48549,10 +48875,10 @@ __metadata: languageName: node linkType: hard -"undici@npm:^7.2.3": - version: 7.9.0 - resolution: "undici@npm:7.9.0" - checksum: 10/fa92d3d9106612be566e79942174e00426c2e1f9a688fb86c8d1809c84a032c02fc57bd601d2051a45d94bbd312c50221644b58c8186c4f0d64f8ecc7f18b806 +"undici@npm:^7.16.0, undici@npm:^7.2.3": + version: 7.16.0 + resolution: "undici@npm:7.16.0" + checksum: 10/2bb71672b23d3dc0f56f1b7fb6c936e4487a350db46eaafc03f2f9107f99cdf8e51ecdd32e589e2381ef47a64b6369cfb31f328b2c3ea663023aa47bc5258b9e languageName: node linkType: hard @@ -48755,13 +49081,6 @@ __metadata: languageName: node linkType: hard -"universalify@npm:^0.2.0": - version: 0.2.0 - resolution: "universalify@npm:0.2.0" - checksum: 10/e86134cb12919d177c2353196a4cc09981524ee87abf621f7bc8d249dbbbebaec5e7d1314b96061497981350df786e4c5128dbf442eba104d6e765bc260678b5 - languageName: node - linkType: hard - "universalify@npm:^2.0.0": version: 2.0.0 resolution: "universalify@npm:2.0.0" @@ -48785,13 +49104,25 @@ __metadata: languageName: node linkType: hard -"unplugin@npm:^1.3.1": - version: 1.16.1 - resolution: "unplugin@npm:1.16.1" +"unplugin-utils@npm:^0.2.4": + version: 0.2.5 + resolution: "unplugin-utils@npm:0.2.5" dependencies: - acorn: "npm:^8.14.0" + pathe: "npm:^2.0.3" + picomatch: "npm:^4.0.3" + checksum: 10/f9ff443089de3159ccab001d075fa4245a741696182dbe6b9708aa01945ded163bd846a3f4099132a2195de3fcbefe404a05380e4697faa4c5917c89f97a2361 + languageName: node + linkType: hard + +"unplugin@npm:^2.3.5": + version: 2.3.11 + resolution: "unplugin@npm:2.3.11" + dependencies: + "@jridgewell/remapping": "npm:^2.3.5" + acorn: "npm:^8.15.0" + picomatch: "npm:^4.0.3" webpack-virtual-modules: "npm:^0.6.2" - checksum: 10/4b46d7d2a63d334a45111ba57a266b3f8993ef12a72b77d7b31ffc455e8a9bef9c0e37ea463eb409dbf7ccec0b9868aeb845dd42c690d9288e4b8ac2d90fbefd + checksum: 10/7b4adbfaac8894e8491c452c0b67c612b57e103761e842d9013ebea89a4ae92a78df4ec0aa30e5e3eaaefd47dd287973d5a662271624b7346a15d9236d257f9d languageName: node linkType: hard @@ -48802,6 +49133,73 @@ __metadata: languageName: node linkType: hard +"unrs-resolver@npm:^1.7.11": + version: 1.11.1 + resolution: "unrs-resolver@npm:1.11.1" + dependencies: + "@unrs/resolver-binding-android-arm-eabi": "npm:1.11.1" + "@unrs/resolver-binding-android-arm64": "npm:1.11.1" + "@unrs/resolver-binding-darwin-arm64": "npm:1.11.1" + "@unrs/resolver-binding-darwin-x64": "npm:1.11.1" + "@unrs/resolver-binding-freebsd-x64": "npm:1.11.1" + "@unrs/resolver-binding-linux-arm-gnueabihf": "npm:1.11.1" + "@unrs/resolver-binding-linux-arm-musleabihf": "npm:1.11.1" + "@unrs/resolver-binding-linux-arm64-gnu": "npm:1.11.1" + "@unrs/resolver-binding-linux-arm64-musl": "npm:1.11.1" + "@unrs/resolver-binding-linux-ppc64-gnu": "npm:1.11.1" + "@unrs/resolver-binding-linux-riscv64-gnu": "npm:1.11.1" + "@unrs/resolver-binding-linux-riscv64-musl": "npm:1.11.1" + "@unrs/resolver-binding-linux-s390x-gnu": "npm:1.11.1" + "@unrs/resolver-binding-linux-x64-gnu": "npm:1.11.1" + "@unrs/resolver-binding-linux-x64-musl": "npm:1.11.1" + "@unrs/resolver-binding-wasm32-wasi": "npm:1.11.1" + "@unrs/resolver-binding-win32-arm64-msvc": "npm:1.11.1" + "@unrs/resolver-binding-win32-ia32-msvc": "npm:1.11.1" + "@unrs/resolver-binding-win32-x64-msvc": "npm:1.11.1" + napi-postinstall: "npm:^0.3.0" + dependenciesMeta: + "@unrs/resolver-binding-android-arm-eabi": + optional: true + "@unrs/resolver-binding-android-arm64": + optional: true + "@unrs/resolver-binding-darwin-arm64": + optional: true + "@unrs/resolver-binding-darwin-x64": + optional: true + "@unrs/resolver-binding-freebsd-x64": + optional: true + "@unrs/resolver-binding-linux-arm-gnueabihf": + optional: true + "@unrs/resolver-binding-linux-arm-musleabihf": + optional: true + "@unrs/resolver-binding-linux-arm64-gnu": + optional: true + "@unrs/resolver-binding-linux-arm64-musl": + optional: true + "@unrs/resolver-binding-linux-ppc64-gnu": + optional: true + "@unrs/resolver-binding-linux-riscv64-gnu": + optional: true + "@unrs/resolver-binding-linux-riscv64-musl": + optional: true + "@unrs/resolver-binding-linux-s390x-gnu": + optional: true + "@unrs/resolver-binding-linux-x64-gnu": + optional: true + "@unrs/resolver-binding-linux-x64-musl": + optional: true + "@unrs/resolver-binding-wasm32-wasi": + optional: true + "@unrs/resolver-binding-win32-arm64-msvc": + optional: true + "@unrs/resolver-binding-win32-ia32-msvc": + optional: true + "@unrs/resolver-binding-win32-x64-msvc": + optional: true + checksum: 10/4de653508cbaae47883a896bd5cdfef0e5e87b428d62620d16fd35cd534beaebf08ebf0cf2f8b4922aa947b2fe745180facf6cc3f39ba364f7ce0f974cb06a70 + languageName: node + linkType: hard + "untildify@npm:^4.0.0": version: 4.0.0 resolution: "untildify@npm:4.0.0" @@ -48816,17 +49214,17 @@ __metadata: languageName: node linkType: hard -"update-browserslist-db@npm:^1.1.1": - version: 1.1.1 - resolution: "update-browserslist-db@npm:1.1.1" +"update-browserslist-db@npm:^1.1.4": + version: 1.1.4 + resolution: "update-browserslist-db@npm:1.1.4" dependencies: escalade: "npm:^3.2.0" - picocolors: "npm:^1.1.0" + picocolors: "npm:^1.1.1" peerDependencies: browserslist: ">= 4.21.0" bin: update-browserslist-db: cli.js - checksum: 10/7678dd8609750588d01aa7460e8eddf2ff9d16c2a52fb1811190e0d056390f1fdffd94db3cf8fb209cf634ab4fa9407886338711c71cc6ccade5eeb22b093734 + checksum: 10/79b2c0a31e9b837b49dc55d5cb7b77f44a69502847c7be352a44b1d35ac2032bf0e1bb7543f992809ed427bf9d32aa3f7ad41cef96198fa959c1666870174c06 languageName: node linkType: hard @@ -48893,7 +49291,7 @@ __metadata: languageName: node linkType: hard -"url-parse@npm:^1.5.10, url-parse@npm:^1.5.3": +"url-parse@npm:^1.5.10": version: 1.5.10 resolution: "url-parse@npm:1.5.10" dependencies: @@ -48977,13 +49375,13 @@ __metadata: languageName: node linkType: hard -"use-immer@npm:^0.10.0": - version: 0.10.0 - resolution: "use-immer@npm:0.10.0" +"use-immer@npm:^0.11.0": + version: 0.11.0 + resolution: "use-immer@npm:0.11.0" peerDependencies: immer: ">=8.0.0" - react: ^16.8.0 || ^17.0.1 || ^18.0.0 - checksum: 10/372b0eea0a05e9435f5dc57a877ec619ea9a479fc82423f502b4a498d5697f8b06b85d089058db5a056bc8bbdb8e6f9ea8c9850b51a6b05d5c63ab0c8eeb2b7e + react: ^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 + checksum: 10/09ddbddec5bd5a939dc462de4c9d1ce47989bc60daa712c5235664650b132e246b8e61b6bd3dc8179649445b7cf389065373c75f23829a676dbda6b5d2428928 languageName: node linkType: hard @@ -49050,12 +49448,12 @@ __metadata: languageName: node linkType: hard -"use-sync-external-store@npm:^1.2.0, use-sync-external-store@npm:^1.4.0": - version: 1.4.0 - resolution: "use-sync-external-store@npm:1.4.0" +"use-sync-external-store@npm:^1.4.0, use-sync-external-store@npm:^1.5.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/08bf581a8a2effaefc355e9d18ed025d436230f4cc973db2f593166df357cf63e47b9097b6e5089b594758bde322e1737754ad64905e030d70f8ff7ee671fd01 + checksum: 10/b40ad2847ba220695bff2d4ba4f4d60391c0fb4fb012faa7a4c18eb38b69181936f5edc55a522c4d20a788d1a879b73c3810952c9d0fd128d01cb3f22042c09e languageName: node linkType: hard @@ -49100,15 +49498,6 @@ __metadata: languageName: node linkType: hard -"uuid@npm:11.1.0, uuid@npm:^11.0.0, uuid@npm:^11.0.2, uuid@npm:^11.0.3": - version: 11.1.0 - resolution: "uuid@npm:11.1.0" - bin: - uuid: dist/esm/bin/uuid - checksum: 10/d2da43b49b154d154574891ced66d0c83fc70caaad87e043400cf644423b067542d6f3eb641b7c819224a7cd3b4c2f21906acbedd6ec9c6a05887aa9115a9cf5 - languageName: node - linkType: hard - "uuid@npm:8.3.2, uuid@npm:^8.0.0, uuid@npm:^8.3.0, uuid@npm:^8.3.2": version: 8.3.2 resolution: "uuid@npm:8.3.2" @@ -49127,6 +49516,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^11.0.0, uuid@npm:^11.0.2, uuid@npm:^11.0.3": + version: 11.1.0 + resolution: "uuid@npm:11.1.0" + bin: + uuid: dist/esm/bin/uuid + checksum: 10/d2da43b49b154d154574891ced66d0c83fc70caaad87e043400cf644423b067542d6f3eb641b7c819224a7cd3b4c2f21906acbedd6ec9c6a05887aa9115a9cf5 + languageName: node + linkType: hard + "uuid@npm:^3.4.0": version: 3.4.0 resolution: "uuid@npm:3.4.0" @@ -49304,8 +49702,8 @@ __metadata: linkType: hard "vite@npm:^7.1.5": - version: 7.1.5 - resolution: "vite@npm:7.1.5" + version: 7.2.6 + resolution: "vite@npm:7.2.6" dependencies: esbuild: "npm:^0.25.0" fdir: "npm:^6.5.0" @@ -49354,7 +49752,7 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 10/59edeef7e98757a668b2ad8a1731a5657fa83e22a165a36b7359225ea98a9be39b2f486710c0cf5085edb85daee7c8b6b6b0bd85d0ef32a1aa84aef71aabd0f0 + checksum: 10/c640ed9c91957749287af2f483f5b5024a56718ba7cf32b0e2c398772fdf7fdb9fd5f97665c41712d4e9422c14009e283d0b69ac1b64b4f91474625bebe324de languageName: node linkType: hard @@ -49372,15 +49770,6 @@ __metadata: languageName: node linkType: hard -"w3c-hr-time@npm:^1.0.2": - version: 1.0.2 - resolution: "w3c-hr-time@npm:1.0.2" - dependencies: - browser-process-hrtime: "npm:^1.0.0" - checksum: 10/03851d90c236837c24c2983f5a8806a837c6515b21d52e5f29776b07cc08695779303d481454d768308489f00dd9d3232d595acaa5b2686d199465a4d9f7b283 - languageName: node - linkType: hard - "w3c-keyname@npm:^2.2.4": version: 2.2.4 resolution: "w3c-keyname@npm:2.2.4" @@ -49388,15 +49777,6 @@ __metadata: languageName: node linkType: hard -"w3c-xmlserializer@npm:^3.0.0": - version: 3.0.0 - resolution: "w3c-xmlserializer@npm:3.0.0" - dependencies: - xml-name-validator: "npm:^4.0.0" - checksum: 10/b4d73e20be283cc9975573a88979d15c08daa9c00911f8c777ef2af74eea11ba635fec18647ff0374ce880ec32ae573d17bd0f787053fc3085a530345b2feab6 - languageName: node - linkType: hard - "w3c-xmlserializer@npm:^5.0.0": version: 5.0.0 resolution: "w3c-xmlserializer@npm:5.0.0" @@ -49420,6 +49800,13 @@ __metadata: languageName: node linkType: hard +"walk-up-path@npm:^4.0.0": + version: 4.0.0 + resolution: "walk-up-path@npm:4.0.0" + checksum: 10/6a230b20e5de296895116dc12b09dafaec1f72b8060c089533d296e241aff059dfaebe0d015c77467f857e4b40c78e08f7481add76f340233a1f34fa8af9ed63 + languageName: node + linkType: hard + "walkdir@npm:^0.4.1": version: 0.4.1 resolution: "walkdir@npm:0.4.1" @@ -49436,13 +49823,13 @@ __metadata: languageName: node linkType: hard -"watchpack@npm:^2.4.1": - version: 2.4.1 - resolution: "watchpack@npm:2.4.1" +"watchpack@npm:^2.4.4": + version: 2.4.4 + resolution: "watchpack@npm:2.4.4" dependencies: glob-to-regexp: "npm:^0.4.1" graceful-fs: "npm:^4.1.2" - checksum: 10/0736ebd20b75d3931f9b6175c819a66dee29297c1b389b2e178bc53396a6f867ecc2fd5d87a713ae92dcb73e487daec4905beee20ca00a9e27f1184a7c2bca5e + checksum: 10/cfa3473fc12a1a1b88123056941e90c462a67aedc10b242229eeeccdd45ed0b763c3b591caaffb0f7d77295b539b5518bb1ad3bcd891ae6505dfeae4cf51fd15 languageName: node linkType: hard @@ -49512,6 +49899,13 @@ __metadata: languageName: node linkType: hard +"webidl-conversions@npm:^8.0.0": + version: 8.0.0 + resolution: "webidl-conversions@npm:8.0.0" + checksum: 10/8138d1b291c8f311d93de680653b13b04560aa35d83f9606642e746fca39d7dab9cddd9282ade21774115ea332b8b11f008106b82d4a0125e98a49479381aeee + languageName: node + linkType: hard + "webpack-dev-middleware@npm:^7.4.2": version: 7.4.2 resolution: "webpack-dev-middleware@npm:7.4.2" @@ -49586,7 +49980,7 @@ __metadata: languageName: node linkType: hard -"webpack-sources@npm:^3.2.3, webpack-sources@npm:^3.3.3": +"webpack-sources@npm:^3.3.3": version: 3.3.3 resolution: "webpack-sources@npm:3.3.3" checksum: 10/ec5d72607e8068467370abccbfff855c596c098baedbe9d198a557ccf198e8546a322836a6f74241492576adba06100286592993a62b63196832cdb53c8bae91 @@ -49600,9 +49994,9 @@ __metadata: languageName: node linkType: hard -"webpack@npm:^5": - version: 5.101.1 - resolution: "webpack@npm:5.101.1" +"webpack@npm:^5, webpack@npm:~5.103.0": + version: 5.103.0 + resolution: "webpack@npm:5.103.0" dependencies: "@types/eslint-scope": "npm:^3.7.7" "@types/estree": "npm:^1.0.8" @@ -49612,7 +50006,7 @@ __metadata: "@webassemblyjs/wasm-parser": "npm:^1.14.1" acorn: "npm:^8.15.0" acorn-import-phases: "npm:^1.0.3" - browserslist: "npm:^4.24.0" + browserslist: "npm:^4.26.3" chrome-trace-event: "npm:^1.0.2" enhanced-resolve: "npm:^5.17.3" es-module-lexer: "npm:^1.2.1" @@ -49621,56 +50015,20 @@ __metadata: glob-to-regexp: "npm:^0.4.1" graceful-fs: "npm:^4.2.11" json-parse-even-better-errors: "npm:^2.3.1" - loader-runner: "npm:^4.2.0" + loader-runner: "npm:^4.3.1" mime-types: "npm:^2.1.27" neo-async: "npm:^2.6.2" - schema-utils: "npm:^4.3.2" - tapable: "npm:^2.1.1" + schema-utils: "npm:^4.3.3" + tapable: "npm:^2.3.0" terser-webpack-plugin: "npm:^5.3.11" - watchpack: "npm:^2.4.1" + watchpack: "npm:^2.4.4" webpack-sources: "npm:^3.3.3" peerDependenciesMeta: webpack-cli: optional: true bin: webpack: bin/webpack.js - checksum: 10/2cbb76683a959bf7f1c11a593e45bb6a2522254eff78e58106da155d6b08da49a3aca584d57948b473a7a55178adcbd728e98cd572ab535d72694b327896a595 - languageName: node - linkType: hard - -"webpack@npm:~5.96.0": - version: 5.96.1 - resolution: "webpack@npm:5.96.1" - dependencies: - "@types/eslint-scope": "npm:^3.7.7" - "@types/estree": "npm:^1.0.6" - "@webassemblyjs/ast": "npm:^1.12.1" - "@webassemblyjs/wasm-edit": "npm:^1.12.1" - "@webassemblyjs/wasm-parser": "npm:^1.12.1" - acorn: "npm:^8.14.0" - browserslist: "npm:^4.24.0" - chrome-trace-event: "npm:^1.0.2" - enhanced-resolve: "npm:^5.17.1" - es-module-lexer: "npm:^1.2.1" - eslint-scope: "npm:5.1.1" - events: "npm:^3.2.0" - glob-to-regexp: "npm:^0.4.1" - graceful-fs: "npm:^4.2.11" - json-parse-even-better-errors: "npm:^2.3.1" - loader-runner: "npm:^4.2.0" - mime-types: "npm:^2.1.27" - neo-async: "npm:^2.6.2" - schema-utils: "npm:^3.2.0" - tapable: "npm:^2.1.1" - terser-webpack-plugin: "npm:^5.3.10" - watchpack: "npm:^2.4.1" - webpack-sources: "npm:^3.2.3" - peerDependenciesMeta: - webpack-cli: - optional: true - bin: - webpack: bin/webpack.js - checksum: 10/d3419ffd198252e1d0301bd0c072cee93172f3e47937c745aa8202691d2f5d529d4ba4a1965d1450ad89a1bcd3c1f70ae09e57232b0d01dd38d69c1060e964d5 + checksum: 10/0018e77d159da412aa8cc1c3ac1d7c0b44228d0f5ce3939b4f424c04feba69747d8490541bcf8143b358a64afbbd69daad95e573ec9c4a90a99bef55d51dd43e languageName: node linkType: hard @@ -49692,15 +50050,6 @@ __metadata: languageName: node linkType: hard -"whatwg-encoding@npm:^2.0.0": - version: 2.0.0 - resolution: "whatwg-encoding@npm:2.0.0" - dependencies: - iconv-lite: "npm:0.6.3" - checksum: 10/162d712d88fd134a4fe587e53302da812eb4215a1baa4c394dfd86eff31d0a079ff932c05233857997de07481093358d6e7587997358f49b8a580a777be22089 - languageName: node - linkType: hard - "whatwg-encoding@npm:^3.1.1": version: 3.1.1 resolution: "whatwg-encoding@npm:3.1.1" @@ -49724,7 +50073,17 @@ __metadata: languageName: node linkType: hard -"whatwg-url@npm:14.2.0, whatwg-url@npm:^14.0.0": +"whatwg-url@npm:15.1.0, whatwg-url@npm:^15.0.0, whatwg-url@npm:^15.1.0": + version: 15.1.0 + resolution: "whatwg-url@npm:15.1.0" + dependencies: + tr46: "npm:^6.0.0" + webidl-conversions: "npm:^8.0.0" + checksum: 10/9ae5ce70060f2a9ea73799062af6e796ec2477f44bf1a886953b405700e3ab11d15aa0fe7088c4215f839e56a845d5d1c44584ed292a832837a8c8549c566886 + languageName: node + linkType: hard + +"whatwg-url@npm:^14.0.0": version: 14.2.0 resolution: "whatwg-url@npm:14.2.0" dependencies: @@ -49734,16 +50093,6 @@ __metadata: languageName: node linkType: hard -"whatwg-url@npm:^11.0.0": - version: 11.0.0 - resolution: "whatwg-url@npm:11.0.0" - dependencies: - tr46: "npm:^3.0.0" - webidl-conversions: "npm:^7.0.0" - checksum: 10/dfcd51c6f4bfb54685528fb10927f3fd3d7c809b5671beef4a8cdd7b1408a7abf3343a35bc71dab83a1424f1c1e92cc2700d7930d95d231df0fac361de0c7648 - languageName: node - linkType: hard - "whatwg-url@npm:^5.0.0": version: 5.0.0 resolution: "whatwg-url@npm:5.0.0" @@ -49810,7 +50159,7 @@ __metadata: languageName: node linkType: hard -"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.18, which-typed-array@npm:^1.1.2": +"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.19, which-typed-array@npm:^1.1.2": version: 1.1.19 resolution: "which-typed-array@npm:1.1.19" dependencies: @@ -49896,33 +50245,33 @@ __metadata: languageName: node linkType: hard -"winston-transport@npm:^4.5.0, winston-transport@npm:^4.7.0": - version: 4.8.0 - resolution: "winston-transport@npm:4.8.0" +"winston-transport@npm:^4.5.0, winston-transport@npm:^4.7.0, winston-transport@npm:^4.9.0": + version: 4.9.0 + resolution: "winston-transport@npm:4.9.0" dependencies: - logform: "npm:^2.6.1" - readable-stream: "npm:^4.5.2" + logform: "npm:^2.7.0" + readable-stream: "npm:^3.6.2" triple-beam: "npm:^1.3.0" - checksum: 10/930bdc0ec689d5c4f07a262721da80440336f64739d0ce33db801c7142b4fca5be8ef71b725b670bac609de8b6bce405e5c5f84d355f5176a611209b476cee18 + checksum: 10/5946918720baadd7447823929e94cf0935f92c4cff6d9451c6fcb009bd9d20a3b3df9ad606109e79d1e9f4d2ff678477bf09f81cfefce2025baaf27a617129bb languageName: node linkType: hard "winston@npm:^3.13.0, winston@npm:^3.2.1": - version: 3.16.0 - resolution: "winston@npm:3.16.0" + version: 3.18.3 + resolution: "winston@npm:3.18.3" dependencies: "@colors/colors": "npm:^1.6.0" - "@dabh/diagnostics": "npm:^2.0.2" + "@dabh/diagnostics": "npm:^2.0.8" async: "npm:^3.2.3" is-stream: "npm:^2.0.0" - logform: "npm:^2.6.0" + logform: "npm:^2.7.0" one-time: "npm:^1.0.0" readable-stream: "npm:^3.4.0" safe-stable-stringify: "npm:^2.3.1" stack-trace: "npm:0.0.x" triple-beam: "npm:^1.3.0" - winston-transport: "npm:^4.7.0" - checksum: 10/cacec5268a965dcd3752bfe223d36eb003ac86b0cabc3d794521dfa6f937f78c86c8da84a53956c0f32169a55b438f6db0ee2aa028d57a2bcd69e1895a2e9d97 + winston-transport: "npm:^4.9.0" + checksum: 10/0d94690e051c625ff5a2731b3057ddaa709445cc791c707c56f99ad7b5bf3eebe634656f707c01c9409dc21dbc118903464f80e29c0cb438b1cc2739c0135e51 languageName: node linkType: hard @@ -50003,7 +50352,7 @@ __metadata: languageName: node linkType: hard -"write-file-atomic@npm:^4.0.0, write-file-atomic@npm:^4.0.2": +"write-file-atomic@npm:^4.0.0": version: 4.0.2 resolution: "write-file-atomic@npm:4.0.2" dependencies: @@ -50023,7 +50372,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:*, ws@npm:^8.12.0, ws@npm:^8.13.0, ws@npm:^8.17.1, ws@npm:^8.18.0, ws@npm:^8.18.2, ws@npm:^8.8.0": +"ws@npm:*, ws@npm:^8.12.0, ws@npm:^8.13.0, ws@npm:^8.17.1, ws@npm:^8.18.0, ws@npm:^8.18.2, ws@npm:^8.18.3, ws@npm:^8.8.0": version: 8.18.3 resolution: "ws@npm:8.18.3" peerDependencies: @@ -50068,6 +50417,15 @@ __metadata: languageName: node linkType: hard +"wsl-utils@npm:^0.1.0": + version: 0.1.0 + resolution: "wsl-utils@npm:0.1.0" + dependencies: + is-wsl: "npm:^3.1.0" + checksum: 10/de4c92187e04c3c27b4478f410a02e81c351dc85efa3447bf1666f34fc80baacd890a6698ec91995631714086992036013286aea3d77e6974020d40a08e00aec + languageName: node + linkType: hard + "xcase@npm:^2.0.1": version: 2.0.1 resolution: "xcase@npm:2.0.1" @@ -50091,13 +50449,6 @@ __metadata: languageName: node linkType: hard -"xml-name-validator@npm:^4.0.0": - version: 4.0.0 - resolution: "xml-name-validator@npm:4.0.0" - checksum: 10/f9582a3f281f790344a471c207516e29e293c6041b2c20d84dd6e58832cd7c19796c47e108fd4fd4b164a5e72ad94f2268f8ace8231cde4a2c6428d6aa220f92 - languageName: node - linkType: hard - "xml-name-validator@npm:^5.0.0": version: 5.0.0 resolution: "xml-name-validator@npm:5.0.0" @@ -50184,11 +50535,11 @@ __metadata: linkType: hard "yaml@npm:^2.0.0, yaml@npm:^2.0.0-10, yaml@npm:^2.1.1, yaml@npm:^2.2.1, yaml@npm:^2.2.2, yaml@npm:^2.3.2, yaml@npm:^2.3.3, yaml@npm:^2.3.4, yaml@npm:^2.7.0, yaml@npm:^2.8.1": - version: 2.8.1 - resolution: "yaml@npm:2.8.1" + version: 2.8.2 + resolution: "yaml@npm:2.8.2" bin: yaml: bin.mjs - checksum: 10/eae07b3947d405012672ec17ce27348aea7d1fa0534143355d24a43a58f5e05652157ea2182c4fe0604f0540be71f99f1173f9d61018379404507790dff17665 + checksum: 10/4eab0074da6bc5a5bffd25b9b359cf7061b771b95d1b3b571852098380db3b1b8f96e0f1f354b56cc7216aa97cea25163377ccbc33a2e9ce00316fe8d02f4539 languageName: node linkType: hard @@ -50354,13 +50705,6 @@ __metadata: languageName: node linkType: hard -"yocto-queue@npm:^1.0.0": - version: 1.2.1 - resolution: "yocto-queue@npm:1.2.1" - checksum: 10/0843d6c2c0558e5c06e98edf9c17942f25c769e21b519303a5c2adefd5b738c9b2054204dc856ac0cd9d134b1bc27d928ce84fd23c9e2423b7e013d5a6f50577 - languageName: node - linkType: hard - "yoctocolors-cjs@npm:^2.1.2": version: 2.1.2 resolution: "yoctocolors-cjs@npm:2.1.2" @@ -50402,31 +50746,38 @@ __metadata: languageName: node linkType: hard -"zod-to-json-schema@npm:^3.20.4, zod-to-json-schema@npm:^3.21.4, zod-to-json-schema@npm:^3.24.1": - version: 3.24.6 - resolution: "zod-to-json-schema@npm:3.24.6" +"zod-to-json-schema@npm:^3.20.4, zod-to-json-schema@npm:^3.21.4, zod-to-json-schema@npm:^3.25.0": + version: 3.25.0 + resolution: "zod-to-json-schema@npm:3.25.0" peerDependencies: - zod: ^3.24.1 - checksum: 10/a2c30cf1f250aa79a7f975e65b4236d1abafafd63b43c43475057f28ce6e13f4c882391553c656fb426fd09665e6ae293c2439b4ed8600863beda43fb1a56922 + zod: ^3.25 || ^4 + checksum: 10/cb932e20b5b5e64c75b2c34a7e6dae74b727292eab9e014b93c2607378b8cb1b227f80b429053ceb77c8e0dddc338837f9e534b2a658540ff60c9e4ffdc7cc19 languageName: node linkType: hard -"zod-validation-error@npm:^3.0.3, zod-validation-error@npm:^3.4.0": - version: 3.4.1 - resolution: "zod-validation-error@npm:3.4.1" +"zod-validation-error@npm:^3.4.0": + version: 3.5.4 + resolution: "zod-validation-error@npm:3.5.4" peerDependencies: zod: ^3.24.4 - checksum: 10/4975aacc1a931acdbaa3eeaf92fc89a210c6fd14a260d17688ec6302ce478c268c008c61bcdeab51c76191ac627e580f319775a7862be7b9c54c94f2f7cb6ed2 + checksum: 10/eb85392e6fd7af255fb233713b1f038134e66cbaff20d1a52d46bd4210fe7b776d48d7dd2170095fbd2b375f6c41d629109bd5eac245c576083c9cf6e131a20b languageName: node linkType: hard -"zod@npm:^3.22.4, zod@npm:^3.23.8, zod@npm:^3.24.2": +"zod@npm:^3.22.4, zod@npm:^3.24.2": version: 3.25.76 resolution: "zod@npm:3.25.76" checksum: 10/f0c963ec40cd96858451d1690404d603d36507c1fc9682f2dae59ab38b578687d542708a7fdbf645f77926f78c9ed558f57c3d3aa226c285f798df0c4da16995 languageName: node linkType: hard +"zod@npm:^3.25 || ^4.0, zod@npm:^4.1.11": + version: 4.1.13 + resolution: "zod@npm:4.1.13" + checksum: 10/0679190318928f69fcb07751063719de232c663b13955fcdb55db59839569d39f3f29b955cb0cba7af0b724233f88c06b3e84c550397ad4e68f8088fa6799d88 + languageName: node + linkType: hard + "zstd-codec@npm:^0.1.4, zstd-codec@npm:^0.1.5": version: 0.1.5 resolution: "zstd-codec@npm:0.1.5"