diff --git a/.changeset/brave-teeth-reply.md b/.changeset/brave-teeth-reply.md new file mode 100644 index 0000000000..8a500692a7 --- /dev/null +++ b/.changeset/brave-teeth-reply.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Internal refactor to remove remnants of the old backend system diff --git a/.changeset/brown-falcons-own.md b/.changeset/brown-falcons-own.md new file mode 100644 index 0000000000..e38f3bbfbc --- /dev/null +++ b/.changeset/brown-falcons-own.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend-module-pg': patch +--- + +Truncate long docs to fit PG index size limit diff --git a/.changeset/brown-turkeys-send.md b/.changeset/brown-turkeys-send.md new file mode 100644 index 0000000000..87a1d03b01 --- /dev/null +++ b/.changeset/brown-turkeys-send.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Log before provider-orphaning eviction happens diff --git a/.changeset/bui-themer-bui-palette-additions.md b/.changeset/bui-themer-bui-palette-additions.md new file mode 100644 index 0000000000..645641b787 --- /dev/null +++ b/.changeset/bui-themer-bui-palette-additions.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-mui-to-bui': minor +--- + +This is the first release of the Material UI to Backstage UI migration helper plugin. It adds a new page at `/mui-to-bui` that converts an existing MUI v5 theme into Backstage UI (BUI) CSS variables, with live preview and copy/download. diff --git a/.changeset/calm-trains-tie.md b/.changeset/calm-trains-tie.md new file mode 100644 index 0000000000..8c399ba123 --- /dev/null +++ b/.changeset/calm-trains-tie.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': minor +--- + +implement support for direct url for AzureBlobStorageUrlReader search function diff --git a/.changeset/clever-papers-watch.md b/.changeset/clever-papers-watch.md new file mode 100644 index 0000000000..831d4c3455 --- /dev/null +++ b/.changeset/clever-papers-watch.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +remove default selection of tab diff --git a/.changeset/cold-coats-show.md b/.changeset/cold-coats-show.md new file mode 100644 index 0000000000..a048ec308c --- /dev/null +++ b/.changeset/cold-coats-show.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Fix for duplicate search results in entity-facets API call diff --git a/.changeset/cool-baboons-count.md b/.changeset/cool-baboons-count.md new file mode 100644 index 0000000000..c5dac494d9 --- /dev/null +++ b/.changeset/cool-baboons-count.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-graph': patch +--- + +Add missing API implementation for catalog graph plugin in NFS apps. diff --git a/.changeset/create-app-1758639549.md b/.changeset/create-app-1758639549.md new file mode 100644 index 0000000000..b50d431d4b --- /dev/null +++ b/.changeset/create-app-1758639549.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Bumped create-app version. diff --git a/.changeset/create-app-1758718573.md b/.changeset/create-app-1758718573.md new file mode 100644 index 0000000000..b50d431d4b --- /dev/null +++ b/.changeset/create-app-1758718573.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Bumped create-app version. diff --git a/.changeset/create-app-1759243273.md b/.changeset/create-app-1759243273.md new file mode 100644 index 0000000000..b50d431d4b --- /dev/null +++ b/.changeset/create-app-1759243273.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Bumped create-app version. diff --git a/.changeset/curvy-bobcats-melt.md b/.changeset/curvy-bobcats-melt.md new file mode 100644 index 0000000000..a9a5d58e5c --- /dev/null +++ b/.changeset/curvy-bobcats-melt.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-react': patch +--- + +Don't change loading to false until we've actually got some log state diff --git a/.changeset/eager-toes-start.md b/.changeset/eager-toes-start.md new file mode 100644 index 0000000000..f36cb1bb0a --- /dev/null +++ b/.changeset/eager-toes-start.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Correct translation key from "type" to "owner" for owner column in entity table to ensure the right translation is loaded. diff --git a/.changeset/famous-loops-tickle.md b/.changeset/famous-loops-tickle.md new file mode 100644 index 0000000000..ae8c9d80ee --- /dev/null +++ b/.changeset/famous-loops-tickle.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes-react': patch +--- + +The configmaps added to be rendered diff --git a/.changeset/fast-heads-brake.md b/.changeset/fast-heads-brake.md new file mode 100644 index 0000000000..bc9f525520 --- /dev/null +++ b/.changeset/fast-heads-brake.md @@ -0,0 +1,25 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Moved catalog processor and provider disabling and priorities under own config objects. + +This is due to issue with some existing providers, such as GitHub, using array syntax for the provider configuration. + +The new config format is not backwards compatible, so users will need to update their config files. The new format +is as follows: + +```yaml +catalog: + providerOptions: + providerA: + disabled: false + providerB: + disabled: true + processorOptions: + processorA: + disabled: false + priority: 10 + processorB: + disabled: true +``` diff --git a/.changeset/fast-queens-guess.md b/.changeset/fast-queens-guess.md new file mode 100644 index 0000000000..b2e5086e9e --- /dev/null +++ b/.changeset/fast-queens-guess.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Swap base token for semantic token in ItemCardHeader to ensure readability in light mode. diff --git a/.changeset/five-olives-bet.md b/.changeset/five-olives-bet.md new file mode 100644 index 0000000000..8623e780b5 --- /dev/null +++ b/.changeset/five-olives-bet.md @@ -0,0 +1,5 @@ +--- +'@backstage/integration': patch +--- + +remove host from azure blob storage integration type diff --git a/.changeset/giant-weeks-jump.md b/.changeset/giant-weeks-jump.md new file mode 100644 index 0000000000..1b11bd1490 --- /dev/null +++ b/.changeset/giant-weeks-jump.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-org': patch +--- + +Add `initialRelationAggregation` and `showAggregateMembersToggle` options to `EntityMembersListCard` as well to `EntityOwnershipCard` diff --git a/.changeset/heavy-cooks-divide.md b/.changeset/heavy-cooks-divide.md new file mode 100644 index 0000000000..181cd01075 --- /dev/null +++ b/.changeset/heavy-cooks-divide.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications-backend-module-slack': minor +--- + +Adds username as optional config in order to send Slack notifications with a specific username in the case when using one Slack App for more than just Backstage. diff --git a/.changeset/itchy-falcons-leave.md b/.changeset/itchy-falcons-leave.md new file mode 100644 index 0000000000..07188e08c5 --- /dev/null +++ b/.changeset/itchy-falcons-leave.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gcp': patch +--- + +Fix documentation strings to mention GCP instead of Azure diff --git a/.changeset/legal-eagles-jog.md b/.changeset/legal-eagles-jog.md new file mode 100644 index 0000000000..5b520af91f --- /dev/null +++ b/.changeset/legal-eagles-jog.md @@ -0,0 +1,18 @@ +--- +'@backstage/plugin-scaffolder-backend-module-confluence-to-markdown': patch +'@backstage/plugin-scaffolder-backend-module-bitbucket-server': patch +'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch +'@backstage/plugin-scaffolder-backend-module-notifications': patch +'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch +'@backstage/plugin-scaffolder-backend-module-bitbucket': patch +'@backstage/plugin-scaffolder-backend-module-gerrit': patch +'@backstage/plugin-scaffolder-backend-module-github': patch +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +'@backstage/plugin-scaffolder-backend-module-sentry': patch +'@backstage/plugin-scaffolder-backend-module-yeoman': patch +'@backstage/plugin-scaffolder-backend-module-azure': patch +'@backstage/plugin-scaffolder-backend-module-gitea': patch +'@backstage/plugin-scaffolder-backend-module-rails': patch +--- + +Updating import for the `scaffolderActionsExtensionPoint` to be the main export diff --git a/.changeset/modern-pugs-appear.md b/.changeset/modern-pugs-appear.md new file mode 100644 index 0000000000..8b502fb52d --- /dev/null +++ b/.changeset/modern-pugs-appear.md @@ -0,0 +1,19 @@ +--- +'@backstage/cli': patch +--- + +Added a new `--entrypoint` option to the `package start` command, which allows you to specify a custom entry directory/file for development applications. This is particularly useful when maintaining separate dev apps for different versions of your plugin (e.g., stable and alpha). + +**Example usage:** + +Consider the following plugin dev folder structure: + +``` +dev/ + index.tsx + alpha/ + index.ts +``` + +- The default `yarn package start` command uses the `dev/` folder as the entry point and executes `dev/index.tsx` file; +- Running `yarn package start --entrypoint dev/alpha` will instead use `dev/alpha/` as the entry point and execute `dev/alpha/index.ts` file. diff --git a/.changeset/moody-singers-deny.md b/.changeset/moody-singers-deny.md new file mode 100644 index 0000000000..28072baf4b --- /dev/null +++ b/.changeset/moody-singers-deny.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +Enable tooltips on disabled buttons with automatic wrapper diff --git a/.changeset/nice-readers-judge.md b/.changeset/nice-readers-judge.md new file mode 100644 index 0000000000..a70ee8e501 --- /dev/null +++ b/.changeset/nice-readers-judge.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend-module-pg': patch +--- + +Added the < character to the query filter regexp diff --git a/.changeset/pre.json b/.changeset/pre.json index 12e4285e7c..486fc27ac5 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -203,7 +203,55 @@ "@backstage/plugin-techdocs-react": "1.3.3", "@backstage/plugin-user-settings": "0.8.26", "@backstage/plugin-user-settings-backend": "0.3.6", - "@backstage/plugin-user-settings-common": "0.0.1" + "@backstage/plugin-user-settings-common": "0.0.1", + "@backstage/plugin-mui-to-bui": "0.1.0" }, - "changesets": [] + "changesets": [ + "brave-teeth-reply", + "brown-falcons-own", + "brown-turkeys-send", + "bui-themer-bui-palette-additions", + "calm-trains-tie", + "clever-papers-watch", + "cold-coats-show", + "cool-baboons-count", + "create-app-1758639549", + "create-app-1758718573", + "create-app-1759243273", + "curvy-bobcats-melt", + "eager-toes-start", + "famous-loops-tickle", + "fast-heads-brake", + "fast-queens-guess", + "five-olives-bet", + "giant-weeks-jump", + "heavy-cooks-divide", + "hungry-crews-fetch", + "itchy-falcons-leave", + "kind-places-reply", + "legal-eagles-jog", + "modern-pugs-appear", + "moody-singers-deny", + "nice-readers-judge", + "public-sites-admire", + "public-wombats-say", + "rare-states-pay", + "ready-poems-change", + "ready-pots-arrive", + "red-dodos-work", + "red-times-bet", + "salty-words-wash", + "short-aliens-invite", + "six-cooks-battle", + "slimy-signs-agree", + "solid-bikes-leave", + "tame-hairs-smash", + "tender-cups-tap", + "thirty-rules-press", + "tired-mice-cheer", + "twelve-guests-sit", + "unified-theme-attr-stack", + "wet-spiders-wait", + "wide-flies-jog" + ] } diff --git a/.changeset/public-sites-admire.md b/.changeset/public-sites-admire.md new file mode 100644 index 0000000000..f5f3304f9f --- /dev/null +++ b/.changeset/public-sites-admire.md @@ -0,0 +1,6 @@ +--- +'@backstage/core-components': patch +'@backstage/plugin-catalog-graph': patch +--- + +Fixed DependencyGraph `svg` size not adapting to the container size diff --git a/.changeset/public-wombats-say.md b/.changeset/public-wombats-say.md new file mode 100644 index 0000000000..b8a72e8436 --- /dev/null +++ b/.changeset/public-wombats-say.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Fixed dependency graph automatically scrolling forever diff --git a/.changeset/rare-states-pay.md b/.changeset/rare-states-pay.md new file mode 100644 index 0000000000..4f7c639af7 --- /dev/null +++ b/.changeset/rare-states-pay.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-catalog-backend-module-azure': patch +'@backstage/integration': patch +--- + +Adds config definitions for Azure Blob Storage. diff --git a/.changeset/ready-poems-change.md b/.changeset/ready-poems-change.md new file mode 100644 index 0000000000..fc95ddb99a --- /dev/null +++ b/.changeset/ready-poems-change.md @@ -0,0 +1,9 @@ +--- +'@backstage/plugin-scaffolder-node': minor +--- + +**BREAKING** - Marking optional fields as required in the `TaskBroker`, these can be fixed with a no-op `() => void` if you don't want to implement the functions. + +- `cancel`, `recoverTasks` and `retry` are the required methods on the `TaskBroker` interface. + +**NOTE**: If you're affected by this breaking change, please reach out to us in an issue as we're thinking about completely removing the `TaskBroker` extension point soon and would like to hear your use cases for the upcoming re-architecture of the `scaffolder-backend` plugin. diff --git a/.changeset/ready-pots-arrive.md b/.changeset/ready-pots-arrive.md new file mode 100644 index 0000000000..96f13a12ae --- /dev/null +++ b/.changeset/ready-pots-arrive.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Add missing `templatingExtensions` option to RouterProps.contextMenu to allow global control across scaffolder pages diff --git a/.changeset/red-dodos-work.md b/.changeset/red-dodos-work.md new file mode 100644 index 0000000000..1bc92d2d59 --- /dev/null +++ b/.changeset/red-dodos-work.md @@ -0,0 +1,9 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Added new `catalog:validate-entity` action to actions registry. + +This action can be used to validate entities against the software catalog. +This is useful for validating `catalog-info.yaml` file changes locally using the +Backstage MCP server. diff --git a/.changeset/red-times-bet.md b/.changeset/red-times-bet.md new file mode 100644 index 0000000000..29e89fb07f --- /dev/null +++ b/.changeset/red-times-bet.md @@ -0,0 +1,13 @@ +--- +'@backstage/plugin-scaffolder-node': patch +--- + +**BREAKING ALPHA**: We've moved the `scaffolderActionsExtensionPoint` from `/alpha` to the main export. + +```tsx +// before +import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha'; + +// after +import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node'; +``` diff --git a/.changeset/salty-words-wash.md b/.changeset/salty-words-wash.md new file mode 100644 index 0000000000..eec2e85331 --- /dev/null +++ b/.changeset/salty-words-wash.md @@ -0,0 +1,11 @@ +--- +'@backstage/backend-defaults': minor +--- + +Adds support for configuring server-level HTTP options through the +`app-config.yaml` file under the `backend.server` key. Supported options +include `headersTimeout`, `keepAliveTimeout`, `requestTimeout`, `timeout`, +`maxHeadersCount`, and `maxRequestsPerSocket`. + +These are passed directly to the underlying Node.js HTTP server. +If omitted, Node.js defaults are used. diff --git a/.changeset/short-aliens-invite.md b/.changeset/short-aliens-invite.md new file mode 100644 index 0000000000..ae3bbc3831 --- /dev/null +++ b/.changeset/short-aliens-invite.md @@ -0,0 +1,13 @@ +--- +'@backstage/plugin-api-docs': minor +--- + +Remove explicit dependency on `isomorphic-form-data`. + +This explicit dependency was added to address [an issue](https://github.com/swagger-api/swagger-ui/issues/7436) in the +dependency `swagger-ui-react`. That [issue has since been resolved](https://github.com/swagger-api/swagger-ui/issues/7436#issuecomment-889792304), +and `isomorphic-form-data` no longer needs to be declared. + +Additionally, this changeset updates the `swagger-ui-react` dependency to version `5.19.0` or higher, which includes +[compatibility](https://github.com/swagger-api/swagger-ui?tab=readme-ov-file#compatibility) with the latest versions of +the OpenAPI specification. diff --git a/.changeset/six-cooks-battle.md b/.changeset/six-cooks-battle.md new file mode 100644 index 0000000000..ecf856225e --- /dev/null +++ b/.changeset/six-cooks-battle.md @@ -0,0 +1,6 @@ +--- +'@backstage/config-loader': patch +'@backstage/config': patch +--- + +Allow colon to be used as config key. diff --git a/.changeset/slimy-signs-agree.md b/.changeset/slimy-signs-agree.md new file mode 100644 index 0000000000..c4307f0bd7 --- /dev/null +++ b/.changeset/slimy-signs-agree.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications-backend': patch +--- + +Fixed exclude entity reference not working in notification sending diff --git a/.changeset/solid-bikes-leave.md b/.changeset/solid-bikes-leave.md new file mode 100644 index 0000000000..6f1bfe0b2f --- /dev/null +++ b/.changeset/solid-bikes-leave.md @@ -0,0 +1,9 @@ +--- +'@backstage/plugin-scaffolder-node': patch +--- + +**DEPRECATION**: We're going to be working on refactoring a lot of the internals of the Scaffolder backend plugin, and with that comes a lot of deprecations and removals for public types that are making these things hard. + +If you're using these types, please reach out to us either on Discord or a GitHub issue with your use cases. + +- `SerializedTask`, `SerializedTaskEvent`, `TaskBroker`, `TaskContext`, `TaskBrokerDispatchOptions`, `TaskBrokerDispatchResult`, `TaskCompletionState`, `TaskEventType`, `TaskFilter`, `TaskFilters`, `TaskStatus` are the types that have now been marked as deprecated, and will be removed in a future release. diff --git a/.changeset/tame-hairs-smash.md b/.changeset/tame-hairs-smash.md new file mode 100644 index 0000000000..db05708132 --- /dev/null +++ b/.changeset/tame-hairs-smash.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-catalog-backend-module-bitbucket-cloud': patch +'@backstage/plugin-bitbucket-cloud-common': patch +--- + +Allow for passing a `pagelen` parameter to configure the `pagelength` property of the `BitbucketCloudEntityProvider` `searchCode` pagination to resolve [bug](https://jira.atlassian.com/browse/BCLOUD-23644) pertaining to duplicate results being returned. diff --git a/.changeset/tender-cups-tap.md b/.changeset/tender-cups-tap.md new file mode 100644 index 0000000000..e95fe2669e --- /dev/null +++ b/.changeset/tender-cups-tap.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +Improved SearchField component flex layout and animations. Fixed SearchField behavior in Header components by switching from width-based transitions to flex-basis transitions for better responsive behavior. Added new Storybook stories to test SearchField integration with Header component. diff --git a/.changeset/thirty-rules-press.md b/.changeset/thirty-rules-press.md new file mode 100644 index 0000000000..d9968317cb --- /dev/null +++ b/.changeset/thirty-rules-press.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': minor +--- + +Add a new `externalTokenHandlersServiceRef` to allow custom external token validations diff --git a/.changeset/tired-mice-cheer.md b/.changeset/tired-mice-cheer.md new file mode 100644 index 0000000000..929aae0f59 --- /dev/null +++ b/.changeset/tired-mice-cheer.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +Avoid overriding onChange when spreading props diff --git a/.changeset/twelve-guests-sit.md b/.changeset/twelve-guests-sit.md new file mode 100644 index 0000000000..0d75e07f0c --- /dev/null +++ b/.changeset/twelve-guests-sit.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes-react': patch +--- + +Fixes calculation of CPU utilization in the PodTable diff --git a/.changeset/twelve-oranges-grin.md b/.changeset/twelve-oranges-grin.md new file mode 100644 index 0000000000..ab1ede0a0d --- /dev/null +++ b/.changeset/twelve-oranges-grin.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-gitlab': patch +--- + +Fixed an issue in `GitlabDiscoveryEntityProvider` where entity fetching could fail for projects with special characters or that had been renamed or moved. diff --git a/.changeset/unified-theme-attr-stack.md b/.changeset/unified-theme-attr-stack.md new file mode 100644 index 0000000000..003e05018f --- /dev/null +++ b/.changeset/unified-theme-attr-stack.md @@ -0,0 +1,5 @@ +--- +'@backstage/theme': patch +--- + +The `UnifiedThemeProvider` now coordinates theme attributes on the document `body` in case multiple theme providers are rendered. diff --git a/.changeset/wet-spiders-wait.md b/.changeset/wet-spiders-wait.md new file mode 100644 index 0000000000..aaffc92013 --- /dev/null +++ b/.changeset/wet-spiders-wait.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-scaffolder-backend': major +--- + +**BREAKING** - Removing the deprecated types and interfaces, there's no replacement for these types, and hopefully not currently used as they offer no value with the plugin being on the new backend system and no way to consume them. + +Affected types: `CreateWorkerOptions`, `CurrentClaimedTask`, `DatabaseTaskStore`, `DatabaseTaskStoreOptions`, `TaskManager`, `TaskStore`, `TaskStoreCreateTaskOptions`, `TaskStoreCreateTaskResult`, `TaskStoreEmitOptions`, `TaskStoreListEventsOptions`, `TaskStoreRecoverTaskOptions`, `TaskStoreShutDownTaskOptions`, `TaskWorker` and `TemplateActionRegistry`. diff --git a/.changeset/wide-flies-jog.md b/.changeset/wide-flies-jog.md new file mode 100644 index 0000000000..4fbafe66a1 --- /dev/null +++ b/.changeset/wide-flies-jog.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Added missing form fields for the new frontend system. diff --git a/.changeset/yarn-plugin-integration.md b/.changeset/yarn-plugin-integration.md new file mode 100644 index 0000000000..2847e89273 --- /dev/null +++ b/.changeset/yarn-plugin-integration.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Added automatic detection and support for the Backstage Yarn plugin when generating new packages with `yarn new`. When the plugin is installed, new packages will automatically use `backstage:^` ranges for `@backstage/*` dependencies. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 4f0e0ac47d..98073c5128 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -14,7 +14,7 @@ The following files contain guidelines for the project: Before any of these commands can be run, you need to run `yarn install` in the project root. - Build: There is no need to build the project during development, and it is verified automatically in the CI pipeline. -- Test: Use `yarn test ` in the project root to run tests. The path can be either a single file or a directory, and be omitted to run tests for all changed files. +- Test: Use `yarn test --no-watch ` in the project root to run tests. The path can be either a single file or a directory. Always provide a path, avoid running all tests. - Type checking: Use `yarn tsc` in the project root to run the type checker. - Code formatting: Use `yarn prettier --write ` to format code. - Lint: Use `yarn lint --fix` in the project root to run the linter. diff --git a/.github/workflows/verify_chromatic-noop.yml b/.github/workflows/verify_chromatic-noop.yml index 600e8f9bdb..4de52a3258 100644 --- a/.github/workflows/verify_chromatic-noop.yml +++ b/.github/workflows/verify_chromatic-noop.yml @@ -6,16 +6,8 @@ on: pull_request: paths-ignore: - '.github/workflows/verify_chromatic.yml' - - 'storybook/**' - - 'packages/config/src/**' - - 'packages/theme/src/**' - - 'packages/types/src/**' - - 'packages/errors/src/**' - - 'packages/version-bridge/src/**' - - 'packages/test-utils/src/**' - - 'packages/core-app-api/src/**' - - 'packages/core-plugin-api/src/**' - - 'packages/core-components/src/**' + - '.storybook/**' + - 'packages/ui/src/**' - '**/*.stories.tsx' permissions: diff --git a/.github/workflows/verify_chromatic.yml b/.github/workflows/verify_chromatic.yml index 86dbe6477c..0c9f110b4c 100644 --- a/.github/workflows/verify_chromatic.yml +++ b/.github/workflows/verify_chromatic.yml @@ -1,20 +1,15 @@ name: Chromatic on: # NOTE: If you change these you must update verify_storybook-noop.yml as well + workflow_dispatch: + push: + branches: + - master pull_request: paths: - '.github/workflows/verify_chromatic.yml' - '.storybook/**' - - 'packages/ui/src/**' - - 'packages/config/src/**' - - 'packages/theme/src/**' - - 'packages/types/src/**' - - 'packages/errors/src/**' - - 'packages/version-bridge/src/**' - - 'packages/test-utils/src/**' - - 'packages/core-app-api/src/**' - - 'packages/core-plugin-api/src/**' - - 'packages/core-components/src/**' + - 'packages/ui/**' - '**/*.stories.tsx' jobs: @@ -36,7 +31,7 @@ jobs: - name: Checkout code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - fetch-depth: 0 # Required to retrieve git history + fetch-depth: 10000 # Required to retrieve git history - name: Use node.js ${{ matrix.node-version }} uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 @@ -53,6 +48,7 @@ jobs: run: yarn build-storybook:chromatic - name: Run Chromatic + id: chromatic uses: chromaui/action@latest with: token: ${{ secrets.GITHUB_TOKEN }} @@ -61,3 +57,28 @@ jobs: projectToken: chpt_dab72dc0f97d55b storybookBuildDir: dist-storybook onlyChanged: true + externals: | + packages/ui/**/*.css + packages/ui/css/** + packages/ui/src/**/*.css + + - name: Prepare Chromatic Message + if: github.event_name == 'pull_request' && steps.chromatic.outputs.url + id: prepare-message + run: | + if [ "${{ steps.chromatic.outputs.changeCount }}" = "0" ] || [ -z "${{ steps.chromatic.outputs.changeCount }}" ]; then + echo "changes-text=No visual changes detected - [**View build in Chromatic**](${{ steps.chromatic.outputs.buildUrl }})" >> $GITHUB_OUTPUT + else + echo "changes-text=**${{ steps.chromatic.outputs.changeCount }}** visual changes are waiting for review - [**Review changes in Chromatic**](${{ steps.chromatic.outputs.buildUrl }})" >> $GITHUB_OUTPUT + fi + + - name: Post Chromatic Link in PR Comment + if: github.event_name == 'pull_request' && steps.chromatic.outputs.url + uses: mshick/add-pr-comment@v2 + with: + message: | + ## 🎨 Visual Testing with Chromatic + + - ${{ steps.prepare-message.outputs.changes-text }} + - **${{ steps.chromatic.outputs.specCount}}** stories for **${{ steps.chromatic.outputs.componentCount}}** Components - [**Preview changes in Storybook**](${{ steps.chromatic.outputs.storybookUrl }}) + repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/verify_e2e-techdocs.yml b/.github/workflows/verify_e2e-techdocs.yml index 080d14a743..64ce9e7b12 100644 --- a/.github/workflows/verify_e2e-techdocs.yml +++ b/.github/workflows/verify_e2e-techdocs.yml @@ -41,11 +41,10 @@ jobs: with: python-version: '3.9' - - name: install dependencies - run: yarn install --immutable - - - name: generate types - run: yarn tsc + - name: yarn install + uses: backstage/actions/yarn-install@b3c1841fd69e1658ac631afafd0fb140a2309024 # v0.6.17 + with: + cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} - name: build techdocs-cli working-directory: packages/techdocs-cli diff --git a/.yarn/patches/@storybook-react-npm-9.1.5-2331f18b6b.patch b/.yarn/patches/@storybook-react-npm-9.1.5-2331f18b6b.patch deleted file mode 100644 index b8fb44001f..0000000000 --- a/.yarn/patches/@storybook-react-npm-9.1.5-2331f18b6b.patch +++ /dev/null @@ -1,35 +0,0 @@ -diff --git a/dist/preview.d.ts b/dist/preview.d.ts -index 73525b5fe07240d27733b59362cf2076cd28a6ed..0420e6b170a65b6d0db64fcfcfa07b68cd851942 100644 ---- a/dist/preview.d.ts -+++ b/dist/preview.d.ts -@@ -174,7 +174,7 @@ type UnionToIntersection = ( - declare function __definePreview[]>(input: { - addons: Addons; - } & ProjectAnnotations>): ReactPreview>; --interface ReactPreview extends Preview { -+interface ReactPreview extends Omit, 'meta'> { - meta, TMetaArgs extends Partial>(meta: { - render?: ArgsStoryFn; - component?: ComponentType; -@@ -187,16 +187,13 @@ interface ReactPreview extends Preview { - }>; - } - type DecoratorsArgs = UnionToIntersection ? TArgs : unknown>; --interface ReactMeta> extends Meta { -+interface ReactMeta extends Omit>, 'story'> { - story ReactTypes['storyResult']) | (StoryAnnotations & { - render: () => ReactTypes['storyResult']; -- })>(story?: TInput): ReactStory ReactTypes['storyResult'] ? { -- render: TInput; -- } : TInput>; -- story, SetOptional>>>(story?: TInput): ReactStory; -+ })>(story?: TInput): ReactStory; -+ story(story?: TInput): ReactStory; - } --interface ReactStory> extends Story { -+interface ReactStory { - Component: ComponentType>; - } -- - export { ReactPreview, ReactStory, __definePreview }; -\ No newline at end of file diff --git a/docs-ui/src/content/components/grid.props.ts b/docs-ui/src/content/components/grid.props.ts index 34d89785cc..6f037cd64f 100644 --- a/docs-ui/src/content/components/grid.props.ts +++ b/docs-ui/src/content/components/grid.props.ts @@ -43,12 +43,12 @@ export const gridItemPropDefs: Record = { values: [...columnsValues, 'full'], responsive: true, }, - start: { + colStart: { type: 'enum | string', values: [...columnsValues, 'auto'], responsive: true, }, - end: { + colEnd: { type: 'enum | string', values: [...columnsValues, 'auto'], responsive: true, @@ -60,7 +60,7 @@ export const gridItemPropDefs: Record = { export const gridUsageSnippet = `import { Grid } from '@backstage/ui'; -`; +`; export const gridDefaultSnippet = ` @@ -68,44 +68,44 @@ export const gridDefaultSnippet = ` `; -export const gridSimpleSnippet = ` +export const gridSimpleSnippet = ` Hello World Hello World Hello World -`; +`; -export const gridComplexSnippet = ` - - Hello World +export const gridComplexSnippet = ` + + Hello World - - Hello World + + Hello World -`; +`; -export const gridMixingRowsSnippet = ` - - Hello World +export const gridMixingRowsSnippet = ` + + Hello World - - Hello World + + Hello World - - Hello World + + Hello World -`; +`; -export const gridResponsiveSnippet = ` +export const gridResponsiveSnippet = ` - Hello World + - Hello World + Hello World -`; +`; -export const gridStartEndSnippet = ` - - Hello World +export const gridStartEndSnippet = ` + + Hello World -`; +`; diff --git a/docs-ui/src/content/components/table.props.ts b/docs-ui/src/content/components/table.props.ts index 1b40055849..d46c6b8ea7 100644 --- a/docs-ui/src/content/components/table.props.ts +++ b/docs-ui/src/content/components/table.props.ts @@ -212,6 +212,11 @@ export const cellPropDefs: Record = { description: "A string representation of the cell's contents, used for features like typeahead.", }, + leadingIcon: { + type: 'enum', + values: ['ReactNode'], + description: 'Optional icon to display before the cell content.', + }, ...classNamePropDefs, ...stylePropDefs, }; diff --git a/docs/assets/architecture-overview/backstage-front-back-arch.jpeg b/docs/assets/architecture-overview/backstage-front-back-arch.jpeg new file mode 100644 index 0000000000..1cca115c95 Binary files /dev/null and b/docs/assets/architecture-overview/backstage-front-back-arch.jpeg differ diff --git a/docs/assets/architecture-overview/simplified-service-based-plugin-architecture.jpeg b/docs/assets/architecture-overview/simplified-service-based-plugin-architecture.jpeg new file mode 100644 index 0000000000..e8f5144c9a Binary files /dev/null and b/docs/assets/architecture-overview/simplified-service-based-plugin-architecture.jpeg differ diff --git a/docs/assets/architecture-overview/simplified-standalone-plugin-architecture.jpeg b/docs/assets/architecture-overview/simplified-standalone-plugin-architecture.jpeg new file mode 100644 index 0000000000..bf96367b17 Binary files /dev/null and b/docs/assets/architecture-overview/simplified-standalone-plugin-architecture.jpeg differ diff --git a/docs/assets/architecture-overview/simplified-third-party-plugin-architecture.jpeg b/docs/assets/architecture-overview/simplified-third-party-plugin-architecture.jpeg new file mode 100644 index 0000000000..4b9fd1d35f Binary files /dev/null and b/docs/assets/architecture-overview/simplified-third-party-plugin-architecture.jpeg differ diff --git a/docs/assets/technical-overview/backstage-ui-group-ownership.png b/docs/assets/technical-overview/backstage-ui-group-ownership.png new file mode 100644 index 0000000000..607aa7ad47 Binary files /dev/null and b/docs/assets/technical-overview/backstage-ui-group-ownership.png differ diff --git a/docs/auth/service-to-service-auth.md b/docs/auth/service-to-service-auth.md index d35aed364d..1423fa92f7 100644 --- a/docs/auth/service-to-service-auth.md +++ b/docs/auth/service-to-service-auth.md @@ -414,9 +414,13 @@ Each entry has one or more of the following fields: ## Adding custom or logic for validation and issuing of tokens -The `pluginTokenHandlerDecoratorServiceRef` can be used to decorate the existing token handler without having to re-implement the entire `AuthService` implementation. +The `pluginTokenHandlerDecoratorServiceRef` and `externalTokenHandlersServiceRef` can be used to extend the existing token handler without having to re-implement the entire `AuthService` implementation. This is particularly useful when you want to add additional logic to the handler, such as logging or metrics or custom token validation. +### PluginTokenHandler decoration + +The `pluginTokenHandlerDecoratorServiceRef` can be used to decorate the default PluginTokenHandler used for create and verify tokens from plugins. + The `PluginTokenHandler` interface has two methods: - `issueToken`: This method is used to issue a token for a plugin. It takes in the `pluginId` and `targetPluginId` as arguments, and an optional `limitedUserToken` object which can be used to issue a token on behalf of another user. The method returns a promise that resolves to an object containing the issued token. @@ -439,3 +443,109 @@ const decoratedPluginTokenHandler = createServiceFactory({ }, }); ``` + +### Adding custom ExternalTokenHandler + +The `externalTokenHandlersServiceRef` can be used to add custom external token handlers to the default implementation. + +Your service factory must return an object with a `type` property that matches the token type in your configuration (e.g., 'custom', 'api-key'). When Backstage encounters tokens of this type, it calls your `initialize` method with all the configuration entries that match this type. Your factory can return either a single token handler or an array of handlers to process and validate these tokens. + +:::note Note + +During token verification, all the token handlers are tested. Consider this when adding many token handlers, as it may impact performance. + +::: + +For example, if we want to add a custom external token handler for the `custom` type: + +our config would look like this: + +```yaml title="in e.g. app-config.production.yaml" +backend: + auth: + externalAccess: + - type: custom + options: + customOptions: additional-value + accessRestrictions: + - plugin: events + - type: custom + options: + customOptions: another-value + accessRestrictions: + - plugin: events +``` + +And we can implement the custom token handler like this: + +```ts +import { + ExternalTokenHandler, + externalTokenHandlersServiceRef, + createExternalTokenHandler, +} from '@backstage/backend-defaults/auth'; +import { createServiceFactory } from '@backstage/backend-plugin-api'; + +const customExternalTokenHandlers = createServiceFactory({ + service: externalTokenHandlersServiceRef, + deps: {}, + async factory() { + return createExternalTokenHandler({ + type: 'custom', + initialize({ options }) { + // Initialize your handler context from config + const customOptions = options.getString('customOptions'); + return { customOptions }; + }, + async verifyToken(token, context) { + // Your custom token validation logic here + // Return undefined if token is invalid + // Return { subject: 'your-subject' } if token is valid + + if (token === 'valid-token') { + return { subject: `custom:${context.customOptions}` }; + } + return undefined; + }, + }); + }, +}); +``` + +The `createExternalTokenHandler` helper simplifies creating external token handlers with the new API: + +- **`type`**: A string identifier for your token handler type that matches the config +- **`initialize`**: Called once for each config entry of this type, receives the config options and returns a context object that will be passed to `verifyToken` +- **`verifyToken`**: Called for each token verification with the token and context, returns the subject if valid or `undefined` if not + +```ts +// Example of a more complex handler with external API call +const apiTokenHandler = createExternalTokenHandler({ + type: 'api-validation', + initialize({ options }) { + const apiBaseUrl = options.getString('apiBaseUrl'); + const apiKey = options.getString('apiKey'); + return { apiBaseUrl, apiKey }; + }, + async verifyToken(token, { apiBaseUrl, apiKey }) { + try { + const response = await fetch(`${apiBaseUrl}/validate-token`, { + method: 'POST', + headers: { + Authorization: `Bearer ${apiKey}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ token }), + }); + + if (response.ok) { + const { userId } = await response.json(); + return { subject: `api:${userId}` }; + } + } catch (error) { + // Log error but don't throw - return undefined for invalid tokens + } + return undefined; + }, +}); +``` diff --git a/docs/backend-system/architecture/03-services.md b/docs/backend-system/architecture/03-services.md index cfcd606d92..022aab202b 100644 --- a/docs/backend-system/architecture/03-services.md +++ b/docs/backend-system/architecture/03-services.md @@ -223,6 +223,35 @@ export const customFooServiceFactory = createServiceFactory({ This allows you to provide more advanced options for the service implementation that couldn't be expressed through static configuration. It also gives users of the service implementation access to other services through dependency injection, which can be useful for their customizations. +## Multiton + +By default the service reference will point to a singleton instance of the service. This mean if a new service factory uses this reference it will override the previous one. This is the most common use-case, but in some cases you may want to have multiple instances of the same service. +For some services, it is desirable to extend the functionality instead of overriding it. For example, some services could have many handlers to address specific events, and you may want to add a new handler instead of overriding the previous one. In this case, you can use the `multiton` option when creating the service reference: + +```ts +// example-service-ref.ts +import { createServiceRef } from '@backstage/backend-plugin-api'; + +export interface FooService { + foo(options: FooOptions): Promise; +} + +export const fooServiceRef = createServiceRef({ + id: 'example.foo', + multiton: true, // this service ref will be an array of instances +}); +``` + +When adding this `serviceRef` as a dependency to a factory, the factory will receive an array of instances instead of a single instance: + +```ts +deps: {fooServices: fooServiceRef}, + factory(fooServices) { + // fooServices is an array of instances + return new Bar(fooServices); + }, +``` + ## Service Factory Options Pattern :::note Note diff --git a/docs/backend-system/building-backends/08-migrating.md b/docs/backend-system/building-backends/08-migrating.md index c07862cc3c..a638516165 100644 --- a/docs/backend-system/building-backends/08-migrating.md +++ b/docs/backend-system/building-backends/08-migrating.md @@ -211,7 +211,7 @@ argument of `createBackend`. ## Removing `@backstage/backend-common` -The `@backstage/backend-common` package has been deprecated as part of moving to the new backend system, an you will need to replace existing usage of it. All exports from the package have been marked as deprecated in the last few releases of the package, and each export has its own deprecation message that explains how to replace that particular export. +The `@backstage/backend-common` package has been deprecated as part of moving to the new backend system, and you will need to replace existing usage of it. All exports from the package have been marked as deprecated in the last few releases of the package, and each export has its own deprecation message that explains how to replace that particular export. These are the deprecation messages for the most common replacements: diff --git a/docs/backend-system/core-services/http-router.md b/docs/backend-system/core-services/http-router.md index 5e065152a0..6de394f549 100644 --- a/docs/backend-system/core-services/http-router.md +++ b/docs/backend-system/core-services/http-router.md @@ -134,7 +134,13 @@ import { createAuthIntegrationRouter, createRateLimitMiddleware, } from '@backstage/backend-defaults/httpRouter'; -import { createServiceFactory } from '@backstage/backend-plugin-api'; +import PromiseRouter from 'express-promise-router'; +import { Handler } from 'express'; +import { + createServiceFactory, + coreServices, + HttpRouterServiceAuthPolicy, +} from '@backstage/backend-plugin-api'; const backend = createBackend(); diff --git a/docs/backend-system/core-services/root-http-router.md b/docs/backend-system/core-services/root-http-router.md index 1e76fb1871..1bda95a990 100644 --- a/docs/backend-system/core-services/root-http-router.md +++ b/docs/backend-system/core-services/root-http-router.md @@ -65,6 +65,20 @@ backend: # - A standard ISO formatted duration string, e.g. 'P2DT6H' or 'PT1M'. # - An object with individual units (in plural) as keys, e.g. `{ days: 2, hours: 6 }`. serverShutdownDelay: { seconds: 20 } + server: + # (Optional) HTTP server configuration, Node.js defaults apply otherwise + # Timeout values support multiple formats: + # - Numbers (milliseconds): 30000 + # - Duration strings: '30s', '1 minute', '2 hours' + # - ISO duration strings: 'PT30S', 'PT1M', 'PT2H' + # - Duration objects: { seconds: 30 }, { minutes: 1 }, { hours: 2 } + headersTimeout: 60000 + requestTimeout: '30s' + keepAliveTimeout: { seconds: 5 } + timeout: 'PT30S' + # Numeric-only settings + maxHeadersCount: 2000 + maxRequestsPerSocket: 100 ``` ### Via Code diff --git a/docs/features/software-catalog/configuration.md b/docs/features/software-catalog/configuration.md index f8de3034fd..5b293cce1c 100644 --- a/docs/features/software-catalog/configuration.md +++ b/docs/features/software-catalog/configuration.md @@ -90,6 +90,35 @@ also write a _custom processor_ to convert between the existing system and Backstage's descriptor format. This is documented in [External Integrations](external-integrations.md). +### Processor configuration + +You can configure processors under the `catalog.processorOptions` key. You can define +options for each processor by its name. With the processor options you can disable +a processor or set its priority. + +```yaml +catalog: + processorOptions: + processorName: + disabled: false # Defaults to false + priority: 100 +``` + +The priority is a number defining the order in which processors are executed. The lower the number, +the higher the priority. The default priority is `20`. + +## Provider configuration + +It's possible to also disable entity providers using the config. You can configure providers +under the `catalog.providerOptions` key. The key is the provider name. + +```yaml +catalog: + providerOptions: + providerName: + disabled: true +``` + ## Catalog Rules By default, the catalog will only allow the ingestion of entities with the kind diff --git a/docs/features/techdocs/addons--new.md b/docs/features/techdocs/addons--new.md index 07138d4b8e..8c58eb892c 100644 --- a/docs/features/techdocs/addons--new.md +++ b/docs/features/techdocs/addons--new.md @@ -1,11 +1,11 @@ --- -id: addons +id: addons--new title: TechDocs Addons description: How to find, use, or create TechDocs Addons. --- :::info -This documentation is written for [the new frontend system](../../frontend-system/index.md) which is still in alpha and is only supported by a small number of plugins. +This documentation is written for [the new frontend system](../../frontend-system/index.md) which is still in alpha and is only supported by a small number of plugins. If you are on the [old frontend system](./getting-started.md#adding-techdocs-frontend-plugin) you may want to read [its own article](./addons.md) instead. ::: ## Concepts diff --git a/docs/integrations/gitlab/discovery.md b/docs/integrations/gitlab/discovery.md index 0fce7b864e..5e02c853fa 100644 --- a/docs/integrations/gitlab/discovery.md +++ b/docs/integrations/gitlab/discovery.md @@ -27,7 +27,7 @@ yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-gitlab Then add the following to your backend initialization: -```ts title="packages/backend/src/index.ts +```ts title="packages/backend/src/index.ts" // optional if you want HTTP endpoints to receive external events // backend.add(import('@backstage/plugin-events-backend')); // optional if you want to use AWS SQS instead of HTTP endpoints to receive external events @@ -49,7 +49,7 @@ Further documentation: - [Events Plugin](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) - [GitLab Module for the Events Plugin](https://github.com/backstage/backstage/blob/master/plugins/events-backend-module-gitlab/README.md) -### Installation with Legacy Backend System +### Installation with Legacy Backend System (skip if you are using Backstage v1.31.0 or later) #### Installation without Events Support diff --git a/docs/notifications/processors.md b/docs/notifications/processors.md index ad3e427364..c25b669ed6 100644 --- a/docs/notifications/processors.md +++ b/docs/notifications/processors.md @@ -148,6 +148,7 @@ notifications: - token: xoxb-XXXXXXXXX broadcastChannels: # Optional, if you wish to support broadcast notifications. - C12345678 + username: 'Backstage Bot' # Optional, defaults to the name of the Slack App. ``` Multiple instances can be added in the `slack` array, allowing you to have multiple configurations if you need to send diff --git a/docs/overview/architecture-overview.md b/docs/overview/architecture-overview.md index 63e2c16e3c..de283beec0 100644 --- a/docs/overview/architecture-overview.md +++ b/docs/overview/architecture-overview.md @@ -10,285 +10,232 @@ Backstage is organized into three main components, each catering to different gr - Core - This includes the base functionality developed by core developers within the open-source project. - App - The app represents a deployed instance of a Backstage application, customized and maintained by app developers, typically a productivity team within an organization. It integrates core functionalities with additional plugins. -- Plugins - These provide additional functionalities to enhance the usefulness of your Backstage app. Plugins can be company-specific or open-sourced and reusable. At Spotify, we have over 100 plugins created by more than 50 different teams, significantly enriching the unified developer experience by incorporating contributions from various infrastructure teams. +- Plugins - These provide additional functionalities to enhance the usefulness of your Backstage app. Plugins can be company-specific or open-sourced and reusable. ## Overview -The following diagram shows how Backstage might look when deployed inside a -company which uses the Tech Radar plugin, the Lighthouse plugin, the CircleCI -plugin and the software catalog. +The following diagram shows a high level view of the overall architecture of Backstage. Running this architecture in a real environment typically involves +containerizing the components. Various commands are provided for accomplishing this. There are 3 main components in this architecture: -1. The core Backstage UI -2. The UI plugins and their backing services -3. Databases +- The [frontend](#frontend-building-blocks) includes the core Backstage [UI](#user-interface) which is an [extension](#extensions) that interacts directly with the user to present the information from the integrated core feature plugins, and other plugins added by a user. +- The [backend](#backend-building-blocks) includes the backend plugins, [core services](https://backstage.io/docs/backend-system/core-services/index), and other services. This is the server-side part of Backstage that is responsible for wiring things together. You can deploy more than one backend, and more than one backend container, depending on your need to scale and isolate individual features. +- Databases host your Backstage data. -Running this architecture in a real environment typically involves -containerizing the components. Various commands are provided for accomplishing -this. +![The architecture of a basic Backstage application](../assets/architecture-overview/backstage-front-back-arch.jpeg) -![The architecture of a basic Backstage application](../assets/architecture-overview/backstage-typical-architecture.png) +## Frontend building blocks -## User Interface +The architectural diagram provides an overview of the different building blocks and the other blocks that each of them interacts with. -The UI is a thin, client-side wrapper around a set of plugins. It provides some -core UI components and libraries for shared activities such as config -management. [[live demo](https://demo.backstage.io/catalog)] +### App + +This is the app instance itself that you create and use as the root of your Backstage frontend application. It does not have any direct functionality in and of itself, but is simply responsible for wiring things together. + +### Extensions + +[Extensions](../frontend-system/architecture/20-extensions.md) are the building blocks that build out both the visual and non-visual structure of the application. There are both built-in extensions provided by the app itself as well as extensions provided by plugins. Each extension is attached to a parent with which it shares data and can have any number of children of its own. It is up to the app to wire together all extensions into a single tree known as the app extension tree. It is from this structure that the entire app can then be instantiated and rendered. + +#### User Interface + +The UI is one of the extensions in the frontend. It is a thin, client-side wrapper around a set of plugins. It provides some core UI components and libraries for shared activities such as config management. [[live demo](https://demo.backstage.io/catalog)] ![UI with different components highlighted](../assets/architecture-overview/core-vs-plugin-components-highlighted.png) -Each plugin typically makes itself available in the UI on a dedicated URL. For -example, the Lighthouse plugin is registered with the UI on `/lighthouse`. -[[learn more](https://backstage.io/blog/2020/04/06/lighthouse-plugin)] +Each plugin typically makes itself available in the UI on a dedicated URL. For example, the Service Catalog plugin is registered with the UI on `/catalog`. -![The lighthouse plugin UI](../assets/architecture-overview/lighthouse-plugin.png) +### Frontend plugins -The CircleCI plugin is available on `/circleci`. +Plugins provide the actual features inside an app. The size of a plugin can range from a tiny component to an entire new system in which other plugins can be composed and integrated. Plugins can be completely standalone or built on top of each other to extend existing plugins and augment their features. Plugins can communicate with each other by composing their extensions or by sharing Utility APIs and routes. -![CircleCI Plugin UI](../assets/architecture-overview/circle-ci.png) +Backstage includes the following set of core plugins: -## Plugins and plugin backends +- [Software Catalog](../features/software-catalog/index.md) - A centralized system that contains metadata for all your software, such as services, websites, libraries, ML models, data pipelines, and so on. It can also contain metadata for the physical or virtual infrastructure needed to operate a piece of software. The software catalog can be viewed and searched through a UI. +- [Software Templates](../features/software-templates/index.md) - A tool to help you create components inside Backstage. A template can load skeletons of code, include some variables, and then publish the template to a location, such as GitHub. +- [TechDocs](https://backstage.io/docs/features/techdocs/) - A docs-like-code solution built into Backstage. Documentation is written in Markdown files which lives together with the code. +- [Kubernetes](../features/kubernetes/index.md) - A tool that allows developers to check the health of their services whether it is on a local host or in production. +- [Search](https://backstage.io/docs/features/search/) - Search for information in the Backstage ecosystem. You can customize the look and feel of each search result and use your own search engine. -Each plugin is a client side application which mounts itself on the UI. Plugins -are written in TypeScript or JavaScript. They each live in their own directory -in the `plugins` folder. For example, the source code for the catalog plugin -is available at -[plugins/catalog](https://github.com/backstage/backstage/tree/master/plugins/catalog). +[Plugin architecture](#plugin-architecture) provides greater detail about the architecture of the plugins themselves. -### Installing plugins +### Extension Overrides -Plugins are typically installed as React components in your Backstage -application. For example, -[here](https://github.com/backstage/backstage/blob/master/packages/app/src/App.tsx) -is a file that imports many full-page plugins in the Backstage sample app. +In addition to the built-in extensions and extensions provided by plugins, it is also possible to install [extension overrides](../frontend-system/architecture/25-extension-overrides.md). This is a collection of extensions with high priority that can replace existing extensions. They can for example be used to override an individual extension provided by a plugin, or install a completely new extension, such as a new app theme. -An example of one of these plugin components is the `CatalogIndexPage`, which is -a full-page view that allows you to browse entities in the Backstage catalog. It -is installed in the app by importing it and adding it as an element like this: +### Utility APIs -```tsx -import { CatalogIndexPage } from '@backstage/plugin-catalog'; +[Utility APIs](../api/utility-apis.md) provide functionality that makes it easier to build plugins, make it possible for plugins to share functionality with other plugins, as well as serve as a customization point for integrators to change the behaviour of the app. Each Utility API is defined by a TypeScript interface as well as a reference used to access the implementations. The implementations of Utility APIs are defined by extensions that are provided and can be overridden the same as any other extension. -... +### Routes -const routes = ( - - ... - } /> - ... - -); -``` +The [Backstage routing system](../frontend-system/architecture/36-routes.md) adds a layer of indirection that makes it possible for plugins to route to each other's extensions without explicit knowledge of what URL paths the extensions are rendered at or if they even exist at all. It makes it possible for plugins to share routes with each other and dynamically generate concrete links at runtime. It is the responsibility of the app to resolve these links to actual URLs, but it is also possible for integrators to define their own route bindings that decide how the links should be resolved. The routing system also lets plugins define internal routes, aiding in the linking to different content in the same plugin. -Note that we use `"/catalog"` as our path to this plugin page, but we can choose -any route we want for the page, as long as it doesn't collide with the routes -that we choose for the other plugins in the app. +## Backend building blocks -These components that are exported from plugins are referred to as "Plugin -Extension Components", or "Extension Components". They are regular React -components, but in addition to being able to be rendered by React, they also -contain various pieces of metadata that is used to wire together the entire app. -Extension components are created using `create*Extension` methods, which you can -read more about in the -[composability documentation](../plugins/composability.md). +The architectural diagram provides an overview of the different building blocks, and the other blocks that each of them interact with. -As of this moment, there is no config based install procedure for plugins. Some -code changes are required. +### Backend -### Plugin architecture +This is the [backend instance](../backend-system/architecture/02-backends.md) itself, which you can think of as the unit of deployment. It does not have any functionality in and of itself, but is simply responsible for wiring things together. + +It is up to you to decide how many different backends you want to deploy. You can have all features in a single one, or split things out into multiple smaller deployments, depending on your need to scale and isolate individual features. + +### Backend plugins + +[Plugins](../backend-system/architecture/04-plugins.md) provide the actual features. They operate completely independently of each other. If plugins want to communicate with each other, they must do so over the wire. There can be no direct communication between plugins through code. Because of this constraint, each plugin can be considered to be its own microservice. + +[Plugin architecture](#plugin-architecture) provides greater detail about the architecture of the plugins themselves. + +### Services + +[Services](../backend-system/architecture/03-services.md) provide utilities to help make it simpler to implement plugins, so that each plugin doesn't need to implement everything from scratch. There are many built-in core services, such as the ones for logging, database access, and reading configuration, but you can also import third-party services, or create your own. + +Services are also a customization point for individual backend installations. You can override services with your own implementations, as well as make smaller customizations to existing services. + +### Extension Points + +Many plugins have ways in which you can extend them, for example entity providers for the Catalog, or custom actions for the Scaffolder. These extension patterns are now encoded into Extension Points. + +[Extension Points](../backend-system/architecture/05-extension-points.md) look a little bit like services, since you depend on them just like you would a service. A key difference is that extension points are registered and provided by plugins or modules themselves, based on what customizations each of them want to expose. + +Extension Points are exported separately from the plugin or module instance itself, and it is possible to expose multiple different extension points at once. This makes it easier to evolve and deprecate individual Extension Points over time, rather than dealing with a single large API surface. + +### Modules + +[Modules](../backend-system/architecture/06-modules.md) use Extension Points to add new features to other plugins or modules. They might for example add an individual Catalog Entity Provider, or one or more Scaffolder Actions. + +Each module may only use Extension Points that belong to a single plugin, and the module must be deployed together with that plugin in the same backend instance. Modules may only communicate with their plugin or other modules through the registered extension points. + +Just like plugins, modules also have access to services and can depend on their own service implementations. They will however share services with the plugin that they extend - there are no module-specific service implementations. + +## Databases + +The databases host your Backstage data. The Backstage backend and its built-in plugins are based on the [Knex](http://knexjs.org/) library, and set up a separate logical database per plugin. This gives great isolation and lets them perform migrations and evolve separately from each other. + +The Knex library supports a multitude of databases, but Backstage at this time of writing is tested primarily against two of them: + +- SQLite, which is mainly used as an in-memory mock/test database +- PostgreSQL, which is the preferred production database. + +Other databases such as the MySQL variants are reported to work but [aren't fully tested](https://github.com/backstage/backstage/issues/2460) yet. + +You can find instructions on setting up a PostgreSQL for your Backstage instance in [Database](../getting-started/config/database.md). [Configuring Plugin Databases](../tutorials/configuring-plugin-databases.md) provides information on how to configure a database for a plugin. + +## Plugin architecture Architecturally, plugins can take three forms: -1. Standalone -2. Service backed -3. Third-party backed +- [Standalone](#standalone-plugins) +- [Service backend](#service-backend-plugins) +- [Third-party backend](#third-party-backend-plugins) -#### Standalone plugins +### Standalone plugins -Standalone plugins run entirely in the browser. -[The Tech Radar plugin](https://demo.backstage.io/tech-radar), for example, -simply renders hard-coded information. It doesn't make any API requests to other -services. +Standalone plugins run entirely in the browser. [The Tech Radar plugin](https://demo.backstage.io/tech-radar), for example, simply renders hard-coded information. It doesn't make any API requests to other services. + +The architecture of the Tech Radar installed into a Backstage app is very simple. You just need to add Tech Radar as a frontend plugin into your app, as shown in the following diagram. + +> **NOTE:** +> The following diagram does not show the detailed contents of the frontend and backend containers in order to highlight the changes that pertain to the addition of the specified plugin. + +![ui and tech radar plugin connected together](../assets/architecture-overview/simplified-standalone-plugin-architecture.jpeg) + +Once the plugin has been added, then you can view the Tech Radar information in the Backstage UI. ![tech radar plugin ui](../assets/architecture-overview/tech-radar-plugin.png) -The architecture of the Tech Radar installed into a Backstage app is very -simple. +### Service backend plugins -![ui and tech radar plugin connected together](../assets/architecture-overview/tech-radar-plugin-architecture.png) +Service backend plugins make API requests to a service which is within the purview of the organization running Backstage. -#### Service backed plugins +The Lighthouse plugin, for example, makes requests to the [lighthouse-audit-service](https://github.com/spotify/lighthouse-audit-service). The `lighthouse-audit-service` is a microservice which runs a copy of Google's [Lighthouse library](https://github.com/GoogleChrome/lighthouse/) and stores the results in a PostgreSQL database. -Service backed plugins make API requests to a service which is within the -purview of the organisation running Backstage. +The Lighthouse plugin is added to the frontend. The lighthouse-audit-service container is already publicly available in Docker Hub and can be downloaded and run with -The Lighthouse plugin, for example, makes requests to the -[lighthouse-audit-service](https://github.com/spotify/lighthouse-audit-service). -The `lighthouse-audit-service` is a microservice which runs a copy of Google's -[Lighthouse library](https://github.com/GoogleChrome/lighthouse/) and stores the -results in a PostgreSQL database. +```bash +docker run spotify/lighthouse-audit-service:latest +``` -Its architecture looks like this: +> **NOTE:** +> The following diagram does not show the detailed contents of the frontend and backend, in order to highlight the changes that pertain to the addition of the specified plugin. -![lighthouse plugin backed to microservice and database](../assets/architecture-overview/lighthouse-plugin-architecture.png) +![lighthouse plugin backend to microservice and database](../assets/architecture-overview/simplified-service-based-plugin-architecture.jpeg) -The software catalog in Backstage is another example of a service backed plugin. -It retrieves a list of services, or "entities", from the Backstage Backend -service and renders them in a table for the user. +The software catalog in Backstage is another example of a service backend plugin. It retrieves a list of services, or "entities", from the Backstage Backend service and renders them in a table for the user. -### Third-party backed plugins +### Third-party backend plugins -Third-party backed plugins are similar to service backed plugins. The main -difference is that the service which backs the plugin is hosted outside of the -ecosystem of the company hosting Backstage. +Third-party backend plugins are similar to service backend plugins. The main difference is that the service which backs the plugin is hosted outside of the ecosystem of the company hosting Backstage. -The CircleCI plugin is an example of a third-party backed plugin. CircleCI is a -SaaS service which can be used without any knowledge of Backstage. It has an API -which a Backstage plugin consumes to display content. +The CircleCI plugin is an example of a third-party backend plugin. CircleCI is a SaaS service which can be used without any knowledge of Backstage. It has an API which a Backstage plugin consumes to display content. -Requests going to CircleCI from the user's browser are passed through a proxy -service that Backstage provides. Without this, the requests would be blocked by -Cross Origin Resource Sharing policies which prevent a browser page served at -[https://example.com](https://example.com) from serving resources hosted at -https://circleci.com. +Requests going to CircleCI from the user's browser are passed through a proxy service that Backstage provides. Without this, the requests would be blocked by Cross Origin Resource Sharing policies which prevent a browser page served at [https://example.com](https://example.com) from serving resources hosted at https://circleci.com. -![CircleCI plugin talking to proxy talking to SaaS Circle CI](../assets/architecture-overview/circle-ci-plugin-architecture.png) +> **NOTE:** +> The following diagram does not show the detailed contents of the frontend and backend, in order to highlight the changes that pertain to the addition of the specified plugin. + +![CircleCI plugin talking to proxy talking to SaaS Circle CI](../assets/architecture-overview/simplified-third-party-plugin-architecture.jpeg) ## Package Architecture -Backstage relies heavily on NPM packages, both for distribution of libraries, -and structuring of code within projects. While the way you structure your -Backstage project is up to you, there is a set of established patterns that we -encourage you to follow. These patterns can help set up a sound project -structure as well as provide familiarity between different Backstage projects. +Backstage relies heavily on NPM packages, both for distribution of libraries, and structuring of code within projects. While the way you structure your Backstage project is up to you, there is a set of established patterns that we encourage you to follow. These patterns can help set up a sound project structure as well as provide familiarity between different Backstage projects. -The following diagram shows an overview of the package architecture of -Backstage. It takes the point of view of an individual plugin and all of the -packages that it may contain, indicated by the thicker border and italic text. -Surrounding the plugin are different package groups which are the different -possible interface points of the plugin. Note that not all library package lists -are complete as packages have been omitted for brevity. +The following diagram shows an overview of the package architecture of Backstage. It takes the point of view of an individual plugin and all of the packages that it may contain, indicated by the thicker border and italic text. Surrounding the plugin are different package groups which are the different possible interface points of the plugin. Note that not all library package lists are complete as packages have been omitted for brevity. ![Package architecture](../assets/architecture-overview/package-architecture.drawio.svg) ### Overview -The arrows in the diagram above indicate a runtime dependency on the code of the -target package. This strict dependency graph only applies to runtime -`dependencies`, and there may be `devDependencies` that break the rules of this -table for the purpose of testing. While there are some arrows that show a -dependency on a collection of frontend, backend and isomorphic packages, those -still have to abide by important compatibility rules shown in the bottom left. +The arrows in the diagram above indicate a runtime dependency on the code of the target package. This strict dependency graph only applies to runtime +`dependencies`, and there may be `devDependencies` that break the rules of this table for the purpose of testing. While there are some arrows that show a dependency on a collection of frontend, backend and isomorphic packages, those still have to abide by important compatibility rules shown in the bottom left. -The `app` and `backend` packages are the entry points of a Backstage project. -The `app` package is the frontend application that brings together a collection -of frontend plugins and customizes them to fit an organization, while the -`backend` package is the backend service that powers the Backstage application. -Worth noting is that there can be more than one instance of each of these -packages within a project. Particularly the `backend` packages can benefit from -being split up into smaller deployment units that each serve their own purpose -with a smaller collection of plugins. +The `app` and `backend` packages are the entry points of a Backstage project. The `app` package is the frontend application that brings together a collection of frontend plugins and customizes them to fit an organization, while the `backend` package is the backend service that powers the Backstage application. Worth noting is that there can be more than one instance of each of these packages within a project. Particularly the `backend` packages can benefit from being split up into smaller deployment units that each serve their own purpose with a smaller collection of plugins. ### Plugin Packages -A typical plugin consists of up to five packages, two frontend ones, two -backend, and one isomorphic package. All packages within the plugin must share a -common prefix, typically of the form `@/plugin-`, but -alternatives like `backstage-plugin-` or -`@/backstage-plugin-` are also valid. Along with this prefix, -each of the packages have their own unique suffix that denotes their role. In -addition to these five plugin packages it's also possible for a plugin to have -additional frontend and backend modules that can be installed to enable optional -features. For a full list of suffixes and their roles, see the -[Plugin Package Structure ADR](../architecture-decisions/adr011-plugin-package-structure.md). +A typical plugin consists of up to five packages, two frontend ones, two backend, and one isomorphic package. All packages within the plugin must share a common prefix, typically of the form `@/plugin-`, but alternatives like `backstage-plugin-` or `@/backstage-plugin-` are also valid. Along with this prefix, each of the packages have their own unique suffix that denotes their role. In addition to these five plugin packages it's also possible for a plugin to have +additional frontend and backend modules that can be installed to enable optional features. For a full list of suffixes and their roles, see the [Plugin Package Structure ADR](../architecture-decisions/adr011-plugin-package-structure.md). -The `-react`, `-common`, and `-node` plugin packages together form the external -library of a plugin. The plugin library enables other plugins to build on top of -and extend a plugin, and likewise allows the plugin to depend on and extend -other plugins. Because of this, it is preferable that plugin library packages -allow duplicate installations of themselves, as you may end up with a mix of -versions being installed as dependencies of various plugins. It is also -forbidden for plugins to directly import non-library packages from other -plugins, all communication between plugins must be handled through libraries and -the application itself. +The `-react`, `-common`, and `-node` plugin packages together form the external library of a plugin. The plugin library enables other plugins to build on top of and extend a plugin, and likewise allows the plugin to depend on and extend other plugins. Because of this, it is preferable that plugin library packages allow duplicate installations of themselves, as you may end up with a mix of versions being installed as dependencies of various plugins. It is also forbidden for plugins to directly import non-library packages from other plugins, all communication between plugins must be handled through libraries and the application itself. ### Frontend Packages -The frontend packages are grouped into two main groups. The first one is -"Frontend App Core", which is the set of packages that are only used by the -`app` package itself. These packages help build up the core structure of the app -as well as provide a foundation for the plugin libraries to rely upon. +The frontend packages are grouped into two main groups. The first one is "Frontend App Core", which is the set of packages that are only used by the `app` package itself. These packages help build up the core structure of the app as well as provide a foundation for the plugin libraries to rely upon. -The second group is the rest of the shared packages, further divided into -"Frontend Plugin Core" and "Frontend Libraries". The core packages are -considered particularly stable and form the core of the frontend framework. -Their most important role is to form the boundary around each plugin and provide -a set of tools that helps you combine a collection of plugins into a running -application. The rest of the frontend packages are more traditional libraries -that serve as building blocks to create plugins. +The second group is the rest of the shared packages, further divided into "Frontend Plugin Core" and "Frontend Libraries". The core packages are considered particularly stable and form the core of the frontend framework. Their most important role is to form the boundary around each plugin and provide a set of tools that helps you combine a collection of plugins into a running application. The rest of the frontend packages are more traditional libraries that serve as building blocks to create plugins. ### Backend Packages -The backend library packages do not currently share a similar plugin -architecture as the frontend packages. They are instead simply a collection of -building blocks and patterns that help you build backend services. This is -however likely to change in the future. +The backend library packages do not currently share a similar plugin architecture as the frontend packages. They are instead simply a collection of building blocks and patterns that help you build backend services. This is however likely to change in the future. ### Common Packages -The common packages are the packages effectively depended on by all other pages. -This is a much smaller set of packages but they are also very pervasive. Because -the common packages are isomorphic and must execute both in the frontend and -backend, they are never allowed to depend on any of the frontend or backend -packages. +The common packages are the packages effectively depended on by all other pages. This is a much smaller set of packages but they are also very pervasive. Because the common packages are isomorphic and must execute both in the frontend and backend, they are never allowed to depend on any of the frontend or backend packages. -The Backstage CLI is in a category of its own and is depended on by virtually -all other packages. It's not a library in itself though, and must always be a -development dependency only. +The Backstage CLI is in a category of its own and is depended on by virtually all other packages. It's not a library in itself though, and must always be a development dependency only. ### Deciding where you place your code -It can sometimes be difficult to decide where to place your plugin code. For example -should it go directly in the `-backend` plugin package or in the `-node` package? -As a general guideline you should try to keep the exposure of your code as low -as possible. If it doesn't need to be public API, it's best to avoid. If you don't -need it to be used by other plugins, then keep it directly in the plugin packages. +It can sometimes be difficult to decide where to place your plugin code. For example, should it go directly in the `-backend` plugin package or in the `-node` package? As a general guideline you should try to keep the exposure of your code as low as possible. If it doesn't need to be public API, it's best to avoid. If you don't need it to be used by other plugins, then keep it directly in the plugin packages. Below is a chart to help you decide where to place your code. ![Package decision](../assets/architecture-overview/package-decision.drawio.svg) -## Databases - -As we have seen, both the `lighthouse-audit-service` and `catalog-backend` -require a database to work with. - -The Backstage backend and its built-in plugins are based on the -[Knex](http://knexjs.org/) library, and set up a separate logical database per -plugin. This gives great isolation and lets them perform migrations and evolve -separately from each other. - -The Knex library supports a multitude of databases, but Backstage at this time -of writing is tested primarily against two of them: SQLite, which is mainly used as -an in-memory mock/test database, and PostgreSQL, which is the preferred -production database. Other databases such as the MySQL variants are reported to -work but -[aren't fully tested](https://github.com/backstage/backstage/issues/2460) -yet. - ## Cache -The Backstage backend and its built-in plugins are also able to leverage cache -stores as a means of improving performance or reliability. Similar to how -databases are supported, plugins receive logically separated cache connections, -which are powered by [Keyv](https://github.com/lukechilds/keyv) under the hood. +The Backstage backend and its built-in plugins are also able to leverage cache stores as a means of improving performance or reliability. Similar to how databases are supported, plugins receive logically separated cache connections, which are powered by [Keyv](https://github.com/lukechilds/keyv) under the hood. -At this time of writing, Backstage can be configured to use one of five cache -stores: memory, which is mainly used for local testing, memcache, redis, valkey or infinispan, -which are cache stores better suited for production deployment. The right cache -store for your Backstage instance will depend on your own run-time constraints -and those required of the plugins you're running. +At this time of writing, Backstage can be configured to use one of five cache stores: + +- `memory` +- `memcache` +- `redis` +- `valkey` +- `infinispan` + +`memory` is primarily used for local development, for production deployments, we recommend using one of the other cache stores. The right cache store for your Backstage instance will depend on your own run-time constraints and those required of the plugins you're running. ### Use memory for cache @@ -351,30 +298,3 @@ backend: ``` Contributions supporting other cache stores are welcome! - -## Containerization - -The example Backstage architecture shown above would Dockerize into three -separate Docker images. - -1. The frontend container -2. The backend container -3. The Lighthouse audit service container - -![Boxes around the architecture to indicate how it is containerised](../assets/architecture-overview/containerised.png) - -The backend container can be built by running the following command: - -```bash -yarn run build -yarn run build-image -``` - -This will create a container called `example-backend`. - -The lighthouse-audit-service container is already publicly available in Docker -Hub and can be downloaded and run with - -```bash -docker run spotify/lighthouse-audit-service:latest -``` diff --git a/docs/overview/technical-overview.md b/docs/overview/technical-overview.md new file mode 100644 index 0000000000..63c20dbf2f --- /dev/null +++ b/docs/overview/technical-overview.md @@ -0,0 +1,88 @@ +--- +id: technical-overview +title: Technical overview +description: Technical overview of Backstage +--- + +## Purpose + +Backstage is an open source framework for building developer portals that was created at Spotify to simplify end-to-end software development. As Spotify grew, their infrastructure became more fragmented and teams couldn't find the APIs they were supposed to use, or who owned a service, or documentation on anything. + +Backstage is powered by a centralized [software catalog](#software-catalog-system-model) and utilizes an abstraction layer that sits on top of all of your infrastructure and developer tooling, allowing you to manage all of your software, services, tooling, and testing in one place. + +Backstage uses a [plugin-architecture](#plugin-architecture-overview) which allows you customize the functionality of your Backstage application using a wide variety of available plugins or you can write your own. It also includes automated templates that your teams can use to create new microservices, helping to ensure consistency and adherence to your best practices. Backstage also provides the ability to create, maintain, and find the documentation for all of your software. + +Backstage is now a [CNCF incubation project](https://backstage.io/blog/2022/03/16/backstage-turns-two#out-of-the-sandbox-and-into-incubation). + +## Benefits + +- For _engineering managers_, it allows you to maintain standards and best practices across the organization, and can help you manage your whole tech ecosystem, from migrations to test certification. +- For _end users_ (developers), it makes it fast and simple to build software components in a standardized way, and it provides a central place to manage all projects and documentation. +- For _platform engineers_, it enables extensibility and scalability by letting you easily integrate new tools and services (via plugins), as well as extending the functionality of existing ones. +- For _everyone_, it is a single, consistent experience that ties all of your infrastructure tooling, resources, standards, owners, contributors, and administrators together in one place. + +If you have question or want support, please join our [Discord server](https://discord.gg/backstage-687207715902193673). + +## Core Features + +Backstage includes the following set of core features: + +- [Authentication and Identity](../auth/index.md) - Sign-in and identification of users, and delegating access to third-party resources, using built-in authentication providers. +- [Kubernetes](../features/kubernetes/index.md) - A tool that allows developers to check the health of their services whether it is on a local host or in production. +- [Notifications](../notifications/index.md) - Provides a means for plugins and external services to send messages to either individual users or groups. +- [Permissions](../permissions/overview.md) - Ability to enforce rules concerning the type of access a user is given to specific data, APIs, or interface actions. +- [Search](https://backstage.io/docs/features/search/) - Search for information in the Backstage ecosystem. You can customize the look and feel of each search result and use your own search engine. +- [Software Catalog](../features/software-catalog/index.md) - A centralized system that contains metadata for all your software, such as services, websites, libraries, ML models, data pipelines, and so on. It can also contain metadata for the physical or virtual infrastructure needed to operate a piece of software. The software catalog can be viewed and searched through a UI. +- [Software Templates](../features/software-templates/index.md) - A tool to help you create components inside Backstage. A template can load skeletons of code, include some variables, and then publish the template to a location, such as GitHub. +- [TechDocs](https://backstage.io/docs/features/techdocs/) - A docs-like-code solution built into Backstage. Documentation is written in Markdown files which lives together with the code. + +## Plugin Architecture Overview + +Plugins are client side applications which mount themselves on the Backstage UI. They allow you to incorporate a wide variety of infrastructure and software development tools into your Backstage application. Backstage uses a [plugin-architecture](../overview/architecture-overview.md#plugin-architecture) to provide a consistent user experience, in a single UI, around all of your plugins. + +The Backstage architecture supports three types of plugins: + +- Standalone - runs entirely in a browser and it does not make any API requests to other services. +- Service backed - makes API requests to a service within the ecosystem of the organization running Backstage. +- Third-party backed - similar to service-backed, but the service backing the plugin is hosted outside of the ecosystem of the company hosting Backstage. + +Many of the features available in Backstage are provided by plugins. For example, the Software Catalog is a service backed plugin. When you view the catalog, it retrieves a set of services ("[entities](#software-catalog-system-model)") from the Backstage Backend service and renders them in a table in the UI for you. + +## Software Catalog System Model + +The system model behind the software catalog is based on [_entities_](../references/glossary.md#entity) and it models two main types: + +- Core Entities +- Organizational Entities + +`Core Entities` include: + +- `Components` - Individual pieces of software that can be tracked in source control and can implement APIs for other components to consume. +- `APIs` - Implemented by components and form the boundaries between different components. The API can be either public, restricted, or private. +- `Resources` - The physical or virtual infrastructure needed to operate a component. + +![core entity relationships in the software model](../assets/software-catalog/software-model-core-entities.drawio.svg) + +`Organizational Entities` include: + +- `User` - A person, such as an employee, contractor, or similar. +- `Group` - An organizational entity, such as a team, business unit, and so on. + +When you have a large catalogue of components, APIs, and resources, it can be difficult to understand how they work together. Ecosystem modeling allows you to organize a large catalog of core entities into: + +- Systems - A collection of resources and components that cooperate to perform a function by exposing one or several public APIs. It hides the resources and private APIs between the components from the consumer. +- Domains - A collection of systems that share terminology, domain models, metrics, KPIs, business purpose, or documentation. + +There are three additional items that can be part of the system model: + +- `Location` - A marker that references other places to look for catalog data. +- `Type` - It has no set meaning. You can assign your own types and use them as desired. +- `Template` - Describes both the parameters that are rendered in the frontend part of the scaffolding wizard, and the steps that are executed when scaffolding that component. + +The following diagram illustrates an example of ecosystem modeling, and provides sample relationships between a domain, system, core entities, and organization entities. + +![ecosystem modeling example](../assets/software-catalog/software-model-entities.drawio.svg) + +The following shows an example of viewing all of the components, APIs, and resources that are managed by your group after setting up the relationships to create a group organizational entity. + +![sample ui showing group ownership](../assets/technical-overview/backstage-ui-group-ownership.png) diff --git a/docs/releases/v1.44.0-next.0-changelog.md b/docs/releases/v1.44.0-next.0-changelog.md new file mode 100644 index 0000000000..601ea2b234 --- /dev/null +++ b/docs/releases/v1.44.0-next.0-changelog.md @@ -0,0 +1,1578 @@ +# Release v1.44.0-next.0 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.44.0-next.0](https://backstage.github.io/upgrade-helper/?to=1.44.0-next.0) + +## @backstage/plugin-scaffolder-backend@3.0.0-next.0 + +### Major Changes + +- 9b81a90: **BREAKING** - Removing the deprecated types and interfaces, there's no replacement for these types, and hopefully not currently used as they offer no value with the plugin being on the new backend system and no way to consume them. + + Affected types: `CreateWorkerOptions`, `CurrentClaimedTask`, `DatabaseTaskStore`, `DatabaseTaskStoreOptions`, `TaskManager`, `TaskStore`, `TaskStoreCreateTaskOptions`, `TaskStoreCreateTaskResult`, `TaskStoreEmitOptions`, `TaskStoreListEventsOptions`, `TaskStoreRecoverTaskOptions`, `TaskStoreShutDownTaskOptions`, `TaskWorker` and `TemplateActionRegistry`. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.0-next.0 + - @backstage/integration@1.18.1-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.14-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.14-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.15-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.14-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.9.1-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.6-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.14-next.0 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.14-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.3-next.0 + - @backstage/plugin-auth-node@0.6.7 + - @backstage/plugin-permission-node@0.10.4 + - @backstage/plugin-scaffolder-common@1.7.2-next.0 + - @backstage/backend-openapi-utils@0.6.1 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.13-next.0 + - @backstage/plugin-catalog-node@1.19.0 + - @backstage/plugin-events-node@0.4.15 + - @backstage/plugin-permission-common@0.9.1 + +## @backstage/backend-defaults@0.13.0-next.0 + +### Minor Changes + +- 2d3e2b2: implement support for direct url for AzureBlobStorageUrlReader search function +- 8b91238: Adds support for configuring server-level HTTP options through the + `app-config.yaml` file under the `backend.server` key. Supported options + include `headersTimeout`, `keepAliveTimeout`, `requestTimeout`, `timeout`, + `maxHeadersCount`, and `maxRequestsPerSocket`. + + These are passed directly to the underlying Node.js HTTP server. + If omitted, Node.js defaults are used. + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/backend-app-api@1.2.7 + - @backstage/plugin-auth-node@0.6.7 + - @backstage/plugin-permission-node@0.10.4 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/cli-node@0.2.14 + - @backstage/config@1.3.3 + - @backstage/config-loader@1.10.3 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.17 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.15 + +## @backstage/plugin-api-docs@0.13.0-next.0 + +### Minor Changes + +- b8a381e: Remove explicit dependency on `isomorphic-form-data`. + + This explicit dependency was added to address [an issue](https://github.com/swagger-api/swagger-ui/issues/7436) in the + dependency `swagger-ui-react`. That [issue has since been resolved](https://github.com/swagger-api/swagger-ui/issues/7436#issuecomment-889792304), + and `isomorphic-form-data` no longer needs to be declared. + + Additionally, this changeset updates the `swagger-ui-react` dependency to version `5.19.0` or higher, which includes + [compatibility](https://github.com/swagger-api/swagger-ui?tab=readme-ov-file#compatibility) with the latest versions of + the OpenAPI specification. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/core-components@0.18.2-next.0 + - @backstage/core-compat-api@0.5.3-next.0 + - @backstage/plugin-catalog@1.31.4-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.11.0 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/plugin-scaffolder-node@0.12.0-next.0 + +### Minor Changes + +- 9b81a90: **BREAKING** - Marking optional fields as required in the `TaskBroker`, these can be fixed with a no-op `() => void` if you don't want to implement the functions. + + - `cancel`, `recoverTasks` and `retry` are the required methods on the `TaskBroker` interface. + + **NOTE**: If you're affected by this breaking change, please reach out to us in an issue as we're thinking about completely removing the `TaskBroker` extension point soon and would like to hear your use cases for the upcoming re-architecture of the `scaffolder-backend` plugin. + +### Patch Changes + +- c8aa210: **BREAKING ALPHA**: We've moved the `scaffolderActionsExtensionPoint` from `/alpha` to the main export. + + ```tsx + // before + import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha'; + + // after + import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node'; + ``` + +- 6e2bda7: **DEPRECATION**: We're going to be working on refactoring a lot of the internals of the Scaffolder backend plugin, and with that comes a lot of deprecations and removals for public types that are making these things hard. + + If you're using these types, please reach out to us either on Discord or a GitHub issue with your use cases. + + - `SerializedTask`, `SerializedTaskEvent`, `TaskBroker`, `TaskContext`, `TaskBrokerDispatchOptions`, `TaskBrokerDispatchResult`, `TaskCompletionState`, `TaskEventType`, `TaskFilter`, `TaskFilters`, `TaskStatus` are the types that have now been marked as deprecated, and will be removed in a future release. + +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/plugin-scaffolder-common@1.7.2-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-model@1.7.5 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.1 + +## @backstage/app-defaults@1.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/core-app-api@1.19.0 + - @backstage/core-plugin-api@1.11.0 + - @backstage/theme@0.6.8 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/backend-dynamic-feature-service@0.7.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.1.1-next.0 + - @backstage/backend-defaults@0.13.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.0 + - @backstage/plugin-auth-node@0.6.7 + - @backstage/plugin-events-backend@0.5.6 + - @backstage/plugin-permission-node@0.10.4 + - @backstage/plugin-search-backend-node@1.3.15 + - @backstage/backend-openapi-utils@0.6.1 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/cli-common@0.1.15 + - @backstage/cli-node@0.2.14 + - @backstage/config@1.3.3 + - @backstage/config-loader@1.10.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-app-node@0.1.37 + - @backstage/plugin-events-node@0.4.15 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/backend-test-utils@1.9.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.0-next.0 + - @backstage/backend-app-api@1.2.7 + - @backstage/plugin-auth-node@0.6.7 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.15 + - @backstage/plugin-permission-common@0.9.1 + +## @backstage/cli@0.34.4-next.0 + +### Patch Changes + +- 6ebc1ea: Fixed module federation config by only setting `import: false` on shared libraries for remote. +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/cli-common@0.1.15 + - @backstage/cli-node@0.2.14 + - @backstage/config@1.3.3 + - @backstage/config-loader@1.10.3 + - @backstage/errors@1.2.7 + - @backstage/eslint-plugin@0.1.11 + - @backstage/release-manifests@0.0.13 + - @backstage/types@1.2.2 + +## @backstage/core-compat-api@0.5.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/core-plugin-api@1.11.0 + - @backstage/version-bridge@1.0.11 + +## @backstage/core-components@0.18.2-next.0 + +### Patch Changes + +- d493126: Swap base token for semantic token in ItemCardHeader to ensure readability in light mode. +- 6981ae6: Fixed DependencyGraph `svg` size not adapting to the container size +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/core-plugin-api@1.11.0 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.8 + - @backstage/version-bridge@1.0.11 + +## @backstage/create-app@0.7.5-next.0 + +### Patch Changes + +- Bumped create-app version. +- Updated dependencies + - @backstage/cli-common@0.1.15 + +## @backstage/dev-utils@1.1.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/core-components@0.18.2-next.0 + - @backstage/app-defaults@1.7.1-next.0 + - @backstage/integration-react@1.2.11-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-app-api@1.19.0 + - @backstage/core-plugin-api@1.11.0 + - @backstage/theme@0.6.8 + +## @backstage/frontend-app-api@0.13.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-defaults@0.3.2-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.19.0 + - @backstage/core-plugin-api@1.11.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/frontend-defaults@0.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/plugin-app@0.3.1-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/frontend-app-api@0.13.1-next.0 + +## @backstage/frontend-dynamic-feature-loader@0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/config@1.3.3 + +## @backstage/frontend-plugin-api@0.12.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/core-plugin-api@1.11.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/frontend-test-utils@0.3.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/plugin-app@0.3.1-next.0 + - @backstage/config@1.3.3 + - @backstage/frontend-app-api@0.13.1-next.0 + - @backstage/test-utils@1.7.11 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/integration@1.18.1-next.0 + +### Patch Changes + +- d772b51: remove host from azure blob storage integration type +- 84443f1: Adds config definitions for Azure Blob Storage. +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + +## @backstage/integration-react@1.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/config@1.3.3 + - @backstage/core-plugin-api@1.11.0 + +## @techdocs/cli@1.9.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.0-next.0 + - @backstage/plugin-techdocs-node@1.13.8-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.3 + +## @backstage/ui@0.7.2-next.0 + +### Patch Changes + +- 827340f: remove default selection of tab +- 9a47125: Improved SearchField component flex layout and animations. Fixed SearchField behavior in Header components by switching from width-based transitions to flex-basis transitions for better responsive behavior. Added new Storybook stories to test SearchField integration with Header component. + +## @backstage/plugin-app@0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/integration-react@1.2.11-next.0 + - @backstage/core-plugin-api@1.11.0 + - @backstage/theme@0.6.8 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/plugin-app-visualizer@0.1.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/core-plugin-api@1.11.0 + +## @backstage/plugin-auth@0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.8 + +## @backstage/plugin-auth-react@0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/core-plugin-api@1.11.0 + - @backstage/errors@1.2.7 + +## @backstage/plugin-bitbucket-cloud-common@0.3.3-next.0 + +### Patch Changes + +- 2aded73: Allow for passing a `pagelen` parameter to configure the `pagelength` property of the `BitbucketCloudEntityProvider` `searchCode` pagination to resolve [bug](https://jira.atlassian.com/browse/BCLOUD-23644) pertaining to duplicate results being returned. +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + +## @backstage/plugin-catalog@1.31.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/core-components@0.18.2-next.0 + - @backstage/core-compat-api@0.5.3-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/integration-react@1.2.11-next.0 + - @backstage/plugin-search-react@1.9.5-next.0 + - @backstage/plugin-techdocs-react@1.3.4-next.0 + - @backstage/plugin-scaffolder-common@1.7.2-next.0 + - @backstage/catalog-client@1.12.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.11.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-catalog-backend@3.1.1-next.0 + +### Patch Changes + +- 9890488: Internal refactor to remove remnants of the old backend system + +- 2aaf01a: Fix for duplicate search results in entity-facets API call + +- e489661: Moved catalog processor and provider disabling and priorities under own config objects. + + This is due to issue with some existing providers, such as GitHub, using array syntax for the provider configuration. + + The new config format is not backwards compatible, so users will need to update their config files. The new format + is as follows: + + ```yaml + catalog: + providerOptions: + providerA: + disabled: false + providerB: + disabled: true + processorOptions: + processorA: + disabled: false + priority: 10 + processorB: + disabled: true + ``` + +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/plugin-permission-node@0.10.4 + - @backstage/backend-openapi-utils@0.6.1 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-client@1.12.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.19.0 + - @backstage/plugin-events-node@0.4.15 + - @backstage/plugin-permission-common@0.9.1 + +## @backstage/plugin-catalog-backend-module-aws@0.4.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.0-next.0 + - @backstage/integration@1.18.1-next.0 + - @backstage/plugin-kubernetes-common@0.9.7-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.17 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.19.0 + +## @backstage/plugin-catalog-backend-module-azure@0.3.10-next.0 + +### Patch Changes + +- 84443f1: Adds config definitions for Azure Blob Storage. +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/config@1.3.3 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.19.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.4-next.0 + +### Patch Changes + +- 2aded73: Allow for passing a `pagelen` parameter to configure the `pagelength` property of the `BitbucketCloudEntityProvider` `searchCode` pagination to resolve [bug](https://jira.atlassian.com/browse/BCLOUD-23644) pertaining to duplicate results being returned. +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.3-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-client@1.12.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.19.0 + - @backstage/plugin-events-node@0.4.15 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-client@1.12.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.19.0 + - @backstage/plugin-events-node@0.4.15 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.9.7-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/plugin-catalog-node@1.19.0 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.19.0 + +## @backstage/plugin-catalog-backend-module-gitea@0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.19.0 + +## @backstage/plugin-catalog-backend-module-github@0.11.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.1.1-next.0 + - @backstage/integration@1.18.1-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-client@1.12.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.19.0 + - @backstage/plugin-events-node@0.4.15 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-github@0.11.1-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/config@1.3.3 + - @backstage/plugin-catalog-node@1.19.0 + - @backstage/plugin-events-node@0.4.15 + +## @backstage/plugin-catalog-backend-module-gitlab@0.7.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.0-next.0 + - @backstage/integration@1.18.1-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.19.0 + - @backstage/plugin-events-node@0.4.15 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-gitlab@0.7.4-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/plugin-catalog-node@1.19.0 + - @backstage/plugin-events-node@0.4.15 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.1.1-next.0 + - @backstage/backend-defaults@0.13.0-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.19.0 + - @backstage/plugin-events-node@0.4.15 + - @backstage/plugin-permission-common@0.9.1 + +## @backstage/plugin-catalog-backend-module-logs@0.1.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.1.1-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/plugin-events-node@0.4.15 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-model@1.7.5 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.19.0 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.7.2-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-model@1.7.5 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.19.0 + +## @backstage/plugin-catalog-graph@0.5.2-next.0 + +### Patch Changes + +- 87b5e6e: Add missing API implementation for catalog graph plugin in NFS apps. +- 6981ae6: Fixed DependencyGraph `svg` size not adapting to the container size +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/core-components@0.18.2-next.0 + - @backstage/core-compat-api@0.5.3-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/catalog-client@1.12.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.11.0 + - @backstage/types@1.2.2 + +## @backstage/plugin-catalog-import@0.13.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/core-components@0.18.2-next.0 + - @backstage/integration@1.18.1-next.0 + - @backstage/core-compat-api@0.5.3-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/integration-react@1.2.11-next.0 + - @backstage/catalog-client@1.12.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-plugin-api@1.11.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.5 + +## @backstage/plugin-catalog-react@1.21.2-next.0 + +### Patch Changes + +- 2a3704d: Correct translation key from "type" to "owner" for owner column in entity table to ensure the right translation is loaded. +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/core-compat-api@0.5.3-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/integration-react@1.2.11-next.0 + - @backstage/catalog-client@1.12.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.11.0 + - @backstage/errors@1.2.7 + - @backstage/frontend-test-utils@0.3.7-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/core-compat-api@0.5.3-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.11.0 + - @backstage/errors@1.2.7 + +## @backstage/plugin-config-schema@0.1.73-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/core-plugin-api@1.11.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-devtools@0.1.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/core-compat-api@0.5.3-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/core-plugin-api@1.11.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-devtools-common@0.1.17 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/plugin-devtools-backend@0.5.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.0-next.0 + - @backstage/plugin-permission-node@0.10.4 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.3 + - @backstage/config-loader@1.10.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-devtools-common@0.1.17 + - @backstage/plugin-permission-common@0.9.1 + +## @backstage/plugin-events-backend-module-github@0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/config@1.3.3 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.15 + +## @backstage/plugin-home@0.8.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/core-components@0.18.2-next.0 + - @backstage/core-compat-api@0.5.3-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/plugin-home-react@0.1.31-next.0 + - @backstage/catalog-client@1.12.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.19.0 + - @backstage/core-plugin-api@1.11.0 + - @backstage/theme@0.6.8 + +## @backstage/plugin-home-react@0.1.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/core-plugin-api@1.11.0 + +## @backstage/plugin-kubernetes@0.12.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/core-components@0.18.2-next.0 + - @backstage/plugin-kubernetes-common@0.9.7-next.0 + - @backstage/core-compat-api@0.5.3-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/plugin-kubernetes-react@0.5.12-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.11.0 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/plugin-kubernetes-backend@0.20.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.9.7-next.0 + - @backstage/plugin-auth-node@0.6.7 + - @backstage/plugin-kubernetes-node@0.3.5-next.0 + - @backstage/plugin-permission-node@0.10.4 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-client@1.12.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.17 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.19.0 + - @backstage/plugin-permission-common@0.9.1 + +## @backstage/plugin-kubernetes-cluster@0.0.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/core-components@0.18.2-next.0 + - @backstage/plugin-kubernetes-common@0.9.7-next.0 + - @backstage/plugin-kubernetes-react@0.5.12-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.11.0 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/plugin-kubernetes-common@0.9.7-next.0 + +### Patch Changes + +- bdd7f95: Make SERVICEACCOUNT_CA_PATH public so it can be imported by external modules. +- Updated dependencies + - @backstage/catalog-model@1.7.5 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.1 + +## @backstage/plugin-kubernetes-node@0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.9.7-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-model@1.7.5 + - @backstage/types@1.2.2 + +## @backstage/plugin-kubernetes-react@0.5.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/plugin-kubernetes-common@0.9.7-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.11.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-mcp-actions-backend@0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.0-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-client@1.12.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.19.0 + +## @backstage/plugin-notifications@0.5.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/core-compat-api@0.5.3-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/core-plugin-api@1.11.0 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.8 + - @backstage/types@1.2.2 + - @backstage/plugin-notifications-common@0.1.0 + - @backstage/plugin-signals-react@0.0.15 + +## @backstage/plugin-org@0.6.45-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/core-components@0.18.2-next.0 + - @backstage/core-compat-api@0.5.3-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.11.0 + - @backstage/plugin-catalog-common@1.1.5 + +## @backstage/plugin-org-react@0.1.43-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/core-components@0.18.2-next.0 + - @backstage/catalog-client@1.12.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.11.0 + +## @backstage/plugin-scaffolder@1.34.2-next.0 + +### Patch Changes + +- e0ffe84: Add missing `templatingExtensions` option to RouterProps.contextMenu to allow global control across scaffolder pages +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/core-components@0.18.2-next.0 + - @backstage/integration@1.18.1-next.0 + - @backstage/core-compat-api@0.5.3-next.0 + - @backstage/plugin-scaffolder-react@1.19.2-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/integration-react@1.2.11-next.0 + - @backstage/plugin-techdocs-react@1.3.4-next.0 + - @backstage/plugin-scaffolder-common@1.7.2-next.0 + - @backstage/catalog-client@1.12.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.11.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.14-next.0 + +### Patch Changes + +- c8aa210: Updating import for the `scaffolderActionsExtensionPoint` to be the main export +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.15-next.0 + +### Patch Changes + +- c8aa210: Updating import for the `scaffolderActionsExtensionPoint` to be the main export +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.14-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.14-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.14-next.0 + +### Patch Changes + +- c8aa210: Updating import for the `scaffolderActionsExtensionPoint` to be the main export +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.3-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.14-next.0 + +### Patch Changes + +- c8aa210: Updating import for the `scaffolderActionsExtensionPoint` to be the main export +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.14-next.0 + +### Patch Changes + +- c8aa210: Updating import for the `scaffolderActionsExtensionPoint` to be the main export +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.16-next.0 + +### Patch Changes + +- c8aa210: Updating import for the `scaffolderActionsExtensionPoint` to be the main export +- Updated dependencies + - @backstage/backend-defaults@0.13.0-next.0 + - @backstage/integration@1.18.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.14-next.0 + +### Patch Changes + +- baf1cab: Fix documentation strings to mention GCP instead of Azure +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.14-next.0 + +### Patch Changes + +- c8aa210: Updating import for the `scaffolderActionsExtensionPoint` to be the main export +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.14-next.0 + +### Patch Changes + +- c8aa210: Updating import for the `scaffolderActionsExtensionPoint` to be the main export +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-github@0.9.1-next.0 + +### Patch Changes + +- c8aa210: Updating import for the `scaffolderActionsExtensionPoint` to be the main export +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.19.0 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.9.6-next.0 + +### Patch Changes + +- c8aa210: Updating import for the `scaffolderActionsExtensionPoint` to be the main export +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.15-next.0 + +### Patch Changes + +- c8aa210: Updating import for the `scaffolderActionsExtensionPoint` to be the main export +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.12.0-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/plugin-notifications-common@0.1.0 + - @backstage/plugin-notifications-node@0.2.19 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.14-next.0 + +### Patch Changes + +- c8aa210: Updating import for the `scaffolderActionsExtensionPoint` to be the main export +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.2.14-next.0 + +### Patch Changes + +- c8aa210: Updating import for the `scaffolderActionsExtensionPoint` to be the main export +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.12.0-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.15-next.0 + +### Patch Changes + +- c8aa210: Updating import for the `scaffolderActionsExtensionPoint` to be the main export +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.12.0-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/types@1.2.2 + - @backstage/plugin-scaffolder-node-test-utils@0.3.4-next.0 + +## @backstage/plugin-scaffolder-common@1.7.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.1 + +## @backstage/plugin-scaffolder-node-test-utils@0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.12.0-next.0 + - @backstage/backend-test-utils@1.9.1-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/types@1.2.2 + +## @backstage/plugin-scaffolder-react@1.19.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/core-components@0.18.2-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/plugin-scaffolder-common@1.7.2-next.0 + - @backstage/catalog-client@1.12.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.11.0 + - @backstage/theme@0.6.8 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-permission-react@0.4.36 + +## @backstage/plugin-search@1.4.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/core-components@0.18.2-next.0 + - @backstage/core-compat-api@0.5.3-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/plugin-search-react@1.9.5-next.0 + - @backstage/core-plugin-api@1.11.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/plugin-search-backend@2.0.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.0-next.0 + - @backstage/plugin-permission-node@0.10.4 + - @backstage/plugin-search-backend-node@1.3.15 + - @backstage/backend-openapi-utils@0.6.1 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/plugin-search-backend-module-pg@0.5.49-next.0 + +### Patch Changes + +- a919ca3: Truncate long docs to fit PG index size limit +- 8d15a51: Added the < character to the query filter regexp +- Updated dependencies + - @backstage/plugin-search-backend-node@1.3.15 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/config@1.3.3 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/plugin-search-backend-module-techdocs@0.4.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.3.15 + - @backstage/plugin-techdocs-node@1.13.8-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-client@1.12.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.19.0 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/plugin-search-react@1.9.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/core-plugin-api@1.11.0 + - @backstage/theme@0.6.8 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.19 + +## @backstage/plugin-signals@0.0.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/core-compat-api@0.5.3-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/core-plugin-api@1.11.0 + - @backstage/theme@0.6.8 + - @backstage/types@1.2.2 + - @backstage/plugin-signals-react@0.0.15 + +## @backstage/plugin-techdocs@1.15.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/core-components@0.18.2-next.0 + - @backstage/integration@1.18.1-next.0 + - @backstage/core-compat-api@0.5.3-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/integration-react@1.2.11-next.0 + - @backstage/plugin-auth-react@0.1.20-next.0 + - @backstage/plugin-search-react@1.9.5-next.0 + - @backstage/plugin-techdocs-react@1.3.4-next.0 + - @backstage/catalog-client@1.12.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-plugin-api@1.11.0 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.8 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-techdocs-addons-test-utils@1.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/plugin-catalog@1.31.4-next.0 + - @backstage/plugin-techdocs@1.15.1-next.0 + - @backstage/integration-react@1.2.11-next.0 + - @backstage/plugin-search-react@1.9.5-next.0 + - @backstage/plugin-techdocs-react@1.3.4-next.0 + - @backstage/core-app-api@1.19.0 + - @backstage/core-plugin-api@1.11.0 + - @backstage/test-utils@1.7.11 + +## @backstage/plugin-techdocs-backend@2.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.0-next.0 + - @backstage/integration@1.18.1-next.0 + - @backstage/plugin-techdocs-node@1.13.8-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-client@1.12.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.19.0 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-search-backend-module-techdocs@0.4.7-next.0 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/integration@1.18.1-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/integration-react@1.2.11-next.0 + - @backstage/plugin-techdocs-react@1.3.4-next.0 + - @backstage/core-plugin-api@1.11.0 + +## @backstage/plugin-techdocs-node@1.13.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.17 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-techdocs-react@1.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-plugin-api@1.11.0 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-techdocs-common@0.1.1 + +## @backstage/plugin-user-settings@0.8.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/core-components@0.18.2-next.0 + - @backstage/core-compat-api@0.5.3-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-app-api@1.19.0 + - @backstage/core-plugin-api@1.11.0 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.8 + - @backstage/types@1.2.2 + - @backstage/plugin-signals-react@0.0.15 + - @backstage/plugin-user-settings-common@0.0.1 + +## @backstage/plugin-user-settings-backend@0.3.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.0-next.0 + - @backstage/plugin-auth-node@0.6.7 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-signals-node@0.1.24 + - @backstage/plugin-user-settings-common@0.0.1 + +## example-app@0.2.114-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.7.2-next.0 + - @backstage/plugin-catalog-graph@0.5.2-next.0 + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/core-components@0.18.2-next.0 + - @backstage/cli@0.34.4-next.0 + - @backstage/plugin-scaffolder@1.34.2-next.0 + - @backstage/plugin-api-docs@0.13.0-next.0 + - @backstage/plugin-catalog@1.31.4-next.0 + - @backstage/plugin-catalog-import@0.13.6-next.0 + - @backstage/plugin-home@0.8.13-next.0 + - @backstage/plugin-kubernetes@0.12.12-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.30-next.0 + - @backstage/plugin-org@0.6.45-next.0 + - @backstage/plugin-scaffolder-react@1.19.2-next.0 + - @backstage/plugin-search@1.4.31-next.0 + - @backstage/plugin-techdocs@1.15.1-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.29-next.0 + - @backstage/plugin-user-settings@0.8.27-next.0 + - @backstage/app-defaults@1.7.1-next.0 + - @backstage/integration-react@1.2.11-next.0 + - @backstage/plugin-auth-react@0.1.20-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.22-next.0 + - @backstage/plugin-devtools@0.1.32-next.0 + - @backstage/plugin-notifications@0.5.10-next.0 + - @backstage/plugin-search-react@1.9.5-next.0 + - @backstage/plugin-signals@0.0.24-next.0 + - @backstage/plugin-techdocs-react@1.3.4-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.19.0 + - @backstage/core-plugin-api@1.11.0 + - @backstage/frontend-app-api@0.13.1-next.0 + - @backstage/theme@0.6.8 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-search-common@1.2.19 + +## example-app-next@0.0.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.7.2-next.0 + - @backstage/plugin-catalog-graph@0.5.2-next.0 + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/core-components@0.18.2-next.0 + - @backstage/cli@0.34.4-next.0 + - @backstage/plugin-scaffolder@1.34.2-next.0 + - @backstage/plugin-api-docs@0.13.0-next.0 + - @backstage/core-compat-api@0.5.3-next.0 + - @backstage/plugin-catalog@1.31.4-next.0 + - @backstage/plugin-catalog-import@0.13.6-next.0 + - @backstage/plugin-home@0.8.13-next.0 + - @backstage/plugin-kubernetes@0.12.12-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.30-next.0 + - @backstage/plugin-org@0.6.45-next.0 + - @backstage/plugin-scaffolder-react@1.19.2-next.0 + - @backstage/plugin-search@1.4.31-next.0 + - @backstage/plugin-techdocs@1.15.1-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.29-next.0 + - @backstage/plugin-user-settings@0.8.27-next.0 + - @backstage/app-defaults@1.7.1-next.0 + - @backstage/frontend-defaults@0.3.2-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/integration-react@1.2.11-next.0 + - @backstage/plugin-app@0.3.1-next.0 + - @backstage/plugin-app-visualizer@0.1.24-next.0 + - @backstage/plugin-auth@0.1.1-next.0 + - @backstage/plugin-auth-react@0.1.20-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.22-next.0 + - @backstage/plugin-notifications@0.5.10-next.0 + - @backstage/plugin-search-react@1.9.5-next.0 + - @backstage/plugin-signals@0.0.24-next.0 + - @backstage/plugin-techdocs-react@1.3.4-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.19.0 + - @backstage/core-plugin-api@1.11.0 + - @backstage/frontend-app-api@0.13.1-next.0 + - @backstage/theme@0.6.8 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-search-common@1.2.19 + +## app-next-example-plugin@0.0.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + +## example-backend@0.0.43-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.1.1-next.0 + - @backstage/backend-defaults@0.13.0-next.0 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.15-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.9.1-next.0 + - @backstage/plugin-scaffolder-backend@3.0.0-next.0 + - @backstage/plugin-app-backend@0.5.6 + - @backstage/plugin-auth-backend@0.25.4 + - @backstage/plugin-auth-backend-module-github-provider@0.3.7 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.0 + - @backstage/plugin-auth-node@0.6.7 + - @backstage/plugin-devtools-backend@0.5.10-next.0 + - @backstage/plugin-events-backend@0.5.6 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.4 + - @backstage/plugin-kubernetes-backend@0.20.3-next.0 + - @backstage/plugin-mcp-actions-backend@0.1.4-next.0 + - @backstage/plugin-notifications-backend@0.5.10 + - @backstage/plugin-permission-backend@0.7.4 + - @backstage/plugin-permission-node@0.10.4 + - @backstage/plugin-proxy-backend@0.6.6 + - @backstage/plugin-search-backend@2.0.7-next.0 + - @backstage/plugin-search-backend-node@1.3.15 + - @backstage/plugin-signals-backend@0.3.8 + - @backstage/plugin-techdocs-backend@2.1.1-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.2.15-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-model@1.7.5 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.12 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.6 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.13-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.4 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.12 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-search-backend-module-catalog@0.3.8 + - @backstage/plugin-search-backend-module-explore@0.3.7 + - @backstage/plugin-search-backend-module-techdocs@0.4.7-next.0 + +## e2e-test@0.2.33-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.7.5-next.0 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + +## @internal/frontend@0.0.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + +## @internal/scaffolder@0.0.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.19.2-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + +## techdocs-cli-embedded-app@0.2.113-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/cli@0.34.4-next.0 + - @backstage/plugin-catalog@1.31.4-next.0 + - @backstage/plugin-techdocs@1.15.1-next.0 + - @backstage/app-defaults@1.7.1-next.0 + - @backstage/integration-react@1.2.11-next.0 + - @backstage/plugin-techdocs-react@1.3.4-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.19.0 + - @backstage/core-plugin-api@1.11.0 + - @backstage/test-utils@1.7.11 + - @backstage/theme@0.6.8 + +## @internal/plugin-todo-list@1.0.44-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/core-plugin-api@1.11.0 diff --git a/docs/releases/v1.44.0-next.1-changelog.md b/docs/releases/v1.44.0-next.1-changelog.md new file mode 100644 index 0000000000..e9d3f16f97 --- /dev/null +++ b/docs/releases/v1.44.0-next.1-changelog.md @@ -0,0 +1,1925 @@ +# Release v1.44.0-next.1 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.44.0-next.1](https://backstage.github.io/upgrade-helper/?to=1.44.0-next.1) + +## @backstage/app-defaults@1.7.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.1-next.0 + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/plugin-permission-react@0.4.37-next.0 + +## @backstage/backend-app-api@1.2.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + +## @backstage/backend-defaults@0.13.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.4-next.0 + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/integration-aws-node@0.1.18-next.0 + - @backstage/backend-app-api@1.2.8-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + - @backstage/plugin-permission-node@0.10.5-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + - @backstage/cli-node@0.2.14 + +## @backstage/backend-dynamic-feature-service@0.7.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.4-next.0 + - @backstage/config@1.3.4-next.0 + - @backstage/backend-defaults@0.13.0-next.1 + - @backstage/plugin-app-node@0.1.38-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + - @backstage/plugin-catalog-backend@3.1.1-next.1 + - @backstage/plugin-events-backend@0.5.7-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-permission-node@0.10.5-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.1 + - @backstage/plugin-search-backend-node@1.3.16-next.0 + - @backstage/backend-openapi-utils@0.6.2-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + - @backstage/cli-node@0.2.14 + - @backstage/plugin-search-common@1.2.20-next.0 + +## @backstage/backend-openapi-utils@0.6.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + +## @backstage/backend-plugin-api@1.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-permission-node@0.10.5-next.0 + +## @backstage/backend-test-utils@1.9.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-defaults@0.13.0-next.1 + - @backstage/backend-app-api@1.2.8-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + +## @backstage/cli@0.34.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.4-next.0 + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/cli-node@0.2.14 + - @backstage/release-manifests@0.0.13 + +## @backstage/config@1.3.4-next.0 + +### Patch Changes + +- b45b094: Allow colon to be used as config key. + +## @backstage/config-loader@1.10.4-next.0 + +### Patch Changes + +- b45b094: Allow colon to be used as config key. +- Updated dependencies + - @backstage/config@1.3.4-next.0 + +## @backstage/core-app-api@1.19.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/core-plugin-api@1.11.1-next.0 + +## @backstage/core-compat-api@0.5.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/plugin-catalog-react@1.21.2-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + +## @backstage/core-components@0.18.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/core-plugin-api@1.11.1-next.0 + +## @backstage/core-plugin-api@1.11.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + +## @backstage/create-app@0.7.5-next.1 + +### Patch Changes + +- Bumped create-app version. + +## @backstage/dev-utils@1.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.1-next.0 + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/integration-react@1.2.11-next.1 + - @backstage/app-defaults@1.7.1-next.1 + - @backstage/plugin-catalog-react@1.21.2-next.1 + +## @backstage/frontend-app-api@0.13.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/core-app-api@1.19.1-next.0 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/frontend-defaults@0.3.2-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + +## @backstage/frontend-defaults@0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/core-components@0.18.2-next.1 + - @backstage/frontend-app-api@0.13.1-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/plugin-app@0.3.1-next.1 + +## @backstage/frontend-dynamic-feature-loader@0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.1 + +## @backstage/frontend-plugin-api@0.12.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + +## @backstage/frontend-test-utils@0.3.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/frontend-app-api@0.13.1-next.1 + - @backstage/test-utils@1.7.12-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/plugin-app@0.3.1-next.1 + +## @backstage/integration@1.18.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + +## @backstage/integration-aws-node@0.1.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + +## @backstage/integration-react@1.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + +## @backstage/repo-tools@0.15.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/cli-node@0.2.14 + +## @techdocs/cli@1.9.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-defaults@0.13.0-next.1 + - @backstage/plugin-techdocs-node@1.13.8-next.1 + +## @backstage/test-utils@1.7.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/core-app-api@1.19.1-next.0 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-permission-react@0.4.37-next.0 + +## @backstage/plugin-api-docs@0.13.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/plugin-permission-react@0.4.37-next.0 + - @backstage/core-compat-api@0.5.3-next.1 + - @backstage/plugin-catalog@1.31.4-next.1 + - @backstage/plugin-catalog-react@1.21.2-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/plugin-catalog-common@1.1.6-next.0 + +## @backstage/plugin-app@0.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/integration-react@1.2.11-next.1 + - @backstage/plugin-permission-react@0.4.37-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.1 + +## @backstage/plugin-app-backend@0.5.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.4-next.0 + - @backstage/config@1.3.4-next.0 + - @backstage/plugin-app-node@0.1.38-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## @backstage/plugin-app-node@0.1.38-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + +## @backstage/plugin-app-visualizer@0.1.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.1 + +## @backstage/plugin-auth-backend@0.25.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## @backstage/plugin-auth-backend-module-auth0-provider@0.2.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-backend@0.25.5-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## @backstage/plugin-auth-backend-module-github-provider@0.3.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.3.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## @backstage/plugin-auth-backend-module-google-provider@0.3.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## @backstage/plugin-auth-backend-module-guest-provider@0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.4.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-backend@0.25.5-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## @backstage/plugin-auth-backend-module-okta-provider@0.2.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## @backstage/plugin-auth-backend-module-openshift-provider@0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + +## @backstage/plugin-auth-node@0.6.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/catalog-client@1.12.0 + +## @backstage/plugin-auth-react@0.1.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + +## @backstage/plugin-catalog@1.31.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/integration-react@1.2.11-next.1 + - @backstage/plugin-permission-react@0.4.37-next.0 + - @backstage/plugin-techdocs-react@1.3.4-next.1 + - @backstage/core-compat-api@0.5.3-next.1 + - @backstage/plugin-catalog-react@1.21.2-next.1 + - @backstage/plugin-search-react@1.9.5-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/plugin-scaffolder-common@1.7.2-next.1 + - @backstage/plugin-catalog-common@1.1.6-next.0 + - @backstage/plugin-search-common@1.2.20-next.0 + - @backstage/catalog-client@1.12.0 + +## @backstage/plugin-catalog-backend@3.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-permission-node@0.10.5-next.0 + - @backstage/backend-openapi-utils@0.6.2-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + - @backstage/catalog-client@1.12.0 + +## @backstage/plugin-catalog-backend-module-aws@0.4.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-defaults@0.13.0-next.1 + - @backstage/integration@1.18.1-next.1 + - @backstage/integration-aws-node@0.1.18-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + - @backstage/plugin-kubernetes-common@0.9.7-next.1 + +## @backstage/plugin-catalog-backend-module-azure@0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/backend-openapi-utils@0.6.2-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + - @backstage/catalog-client@1.12.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + - @backstage/catalog-client@1.12.0 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-kubernetes-common@0.9.7-next.1 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + +## @backstage/plugin-catalog-backend-module-gitea@0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + +## @backstage/plugin-catalog-backend-module-github@0.11.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-backend@3.1.1-next.1 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + - @backstage/catalog-client@1.12.0 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-backend-module-github@0.11.1-next.1 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + +## @backstage/plugin-catalog-backend-module-gitlab@0.7.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-defaults@0.13.0-next.1 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-backend-module-gitlab@0.7.4-next.1 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-defaults@0.13.0-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-backend@3.1.1-next.1 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + +## @backstage/plugin-catalog-backend-module-ldap@0.11.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + +## @backstage/plugin-catalog-backend-module-logs@0.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-backend@3.1.1-next.1 + - @backstage/plugin-events-node@0.4.16-next.0 + +## @backstage/plugin-catalog-backend-module-msgraph@0.8.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-scaffolder-common@1.7.2-next.1 + - @backstage/plugin-catalog-common@1.1.6-next.0 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.6.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.10-next.0 + +## @backstage/plugin-catalog-common@1.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-search-common@1.2.20-next.0 + +## @backstage/plugin-catalog-graph@0.5.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/core-compat-api@0.5.3-next.1 + - @backstage/plugin-catalog-react@1.21.2-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/catalog-client@1.12.0 + +## @backstage/plugin-catalog-import@0.13.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/integration-react@1.2.11-next.1 + - @backstage/core-compat-api@0.5.3-next.1 + - @backstage/plugin-catalog-react@1.21.2-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/plugin-catalog-common@1.1.6-next.0 + - @backstage/catalog-client@1.12.0 + +## @backstage/plugin-catalog-node@1.19.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-permission-node@0.10.5-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + - @backstage/catalog-client@1.12.0 + +## @backstage/plugin-catalog-react@1.21.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/frontend-test-utils@0.3.7-next.1 + - @backstage/integration-react@1.2.11-next.1 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-permission-react@0.4.37-next.0 + - @backstage/core-compat-api@0.5.3-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/plugin-catalog-common@1.1.6-next.0 + - @backstage/catalog-client@1.12.0 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/core-compat-api@0.5.3-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + +## @backstage/plugin-catalog-unprocessed-entities-common@0.0.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.2-next.0 + +## @backstage/plugin-config-schema@0.1.73-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + +## @backstage/plugin-devtools@0.1.32-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/plugin-permission-react@0.4.37-next.0 + - @backstage/core-compat-api@0.5.3-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/plugin-devtools-common@0.1.18-next.0 + +## @backstage/plugin-devtools-backend@0.5.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.4-next.0 + - @backstage/config@1.3.4-next.0 + - @backstage/backend-defaults@0.13.0-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-permission-node@0.10.5-next.0 + - @backstage/plugin-devtools-common@0.1.18-next.0 + +## @backstage/plugin-devtools-common@0.1.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.2-next.0 + +## @backstage/plugin-events-backend@0.5.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/backend-openapi-utils@0.6.2-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + +## @backstage/plugin-events-backend-module-aws-sqs@0.4.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + +## @backstage/plugin-events-backend-module-azure@0.2.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + +## @backstage/plugin-events-backend-module-bitbucket-server@0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + +## @backstage/plugin-events-backend-module-gerrit@0.2.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + +## @backstage/plugin-events-backend-module-github@0.4.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + +## @backstage/plugin-events-backend-module-gitlab@0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + +## @backstage/plugin-events-backend-module-google-pubsub@0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + +## @backstage/plugin-events-backend-module-kafka@0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + +## @backstage/plugin-events-backend-test-utils@0.1.49-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.16-next.0 + +## @backstage/plugin-events-node@0.4.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + +## @backstage/plugin-gateway-backend@1.0.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + +## @backstage/plugin-home@0.8.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/core-app-api@1.19.1-next.0 + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/core-compat-api@0.5.3-next.1 + - @backstage/plugin-catalog-react@1.21.2-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/plugin-home-react@0.1.31-next.1 + - @backstage/catalog-client@1.12.0 + +## @backstage/plugin-home-react@0.1.31-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.1 + +## @backstage/plugin-kubernetes@0.12.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/plugin-permission-react@0.4.37-next.0 + - @backstage/core-compat-api@0.5.3-next.1 + - @backstage/plugin-catalog-react@1.21.2-next.1 + - @backstage/plugin-kubernetes-react@0.5.12-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/plugin-kubernetes-common@0.9.7-next.1 + +## @backstage/plugin-kubernetes-backend@0.20.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration-aws-node@0.1.18-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-permission-node@0.10.5-next.0 + - @backstage/plugin-kubernetes-node@0.3.5-next.1 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-kubernetes-common@0.9.7-next.1 + - @backstage/catalog-client@1.12.0 + +## @backstage/plugin-kubernetes-cluster@0.0.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/plugin-permission-react@0.4.37-next.0 + - @backstage/plugin-catalog-react@1.21.2-next.1 + - @backstage/plugin-kubernetes-react@0.5.12-next.1 + - @backstage/plugin-kubernetes-common@0.9.7-next.1 + +## @backstage/plugin-kubernetes-common@0.9.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.2-next.0 + +## @backstage/plugin-kubernetes-node@0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-kubernetes-common@0.9.7-next.1 + +## @backstage/plugin-kubernetes-react@0.5.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/plugin-kubernetes-common@0.9.7-next.1 + +## @backstage/plugin-mcp-actions-backend@0.1.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.0-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/catalog-client@1.12.0 + +## @backstage/plugin-notifications@0.5.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/plugin-notifications-common@0.1.1-next.0 + - @backstage/core-compat-api@0.5.3-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/plugin-signals-react@0.0.16-next.0 + +## @backstage/plugin-notifications-backend@0.5.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + - @backstage/plugin-notifications-common@0.1.1-next.0 + - @backstage/plugin-signals-node@0.1.25-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + - @backstage/plugin-notifications-node@0.2.20-next.0 + +## @backstage/plugin-notifications-backend-module-email@0.3.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration-aws-node@0.1.18-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-notifications-common@0.1.1-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-notifications-node@0.2.20-next.0 + - @backstage/catalog-client@1.12.0 + +## @backstage/plugin-notifications-backend-module-slack@0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-notifications-common@0.1.1-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-notifications-node@0.2.20-next.0 + +## @backstage/plugin-notifications-common@0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + +## @backstage/plugin-notifications-node@0.2.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-notifications-common@0.1.1-next.0 + - @backstage/plugin-signals-node@0.1.25-next.0 + - @backstage/catalog-client@1.12.0 + +## @backstage/plugin-org@0.6.45-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/core-compat-api@0.5.3-next.1 + - @backstage/plugin-catalog-react@1.21.2-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/plugin-catalog-common@1.1.6-next.0 + +## @backstage/plugin-org-react@0.1.43-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/plugin-catalog-react@1.21.2-next.1 + - @backstage/catalog-client@1.12.0 + +## @backstage/plugin-permission-backend@0.7.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-permission-node@0.10.5-next.0 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-permission-node@0.10.5-next.0 + +## @backstage/plugin-permission-common@0.9.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + +## @backstage/plugin-permission-node@0.10.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + +## @backstage/plugin-permission-react@0.4.37-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + +## @backstage/plugin-proxy-backend@0.6.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-proxy-node@0.1.9-next.0 + +## @backstage/plugin-proxy-node@0.1.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + +## @backstage/plugin-scaffolder@1.34.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.1-next.1 + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/integration-react@1.2.11-next.1 + - @backstage/plugin-permission-react@0.4.37-next.0 + - @backstage/plugin-techdocs-react@1.3.4-next.1 + - @backstage/core-compat-api@0.5.3-next.1 + - @backstage/plugin-catalog-react@1.21.2-next.1 + - @backstage/plugin-scaffolder-react@1.19.2-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/plugin-scaffolder-common@1.7.2-next.1 + - @backstage/plugin-catalog-common@1.1.6-next.0 + - @backstage/catalog-client@1.12.0 + +## @backstage/plugin-scaffolder-backend@3.0.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-defaults@0.13.0-next.1 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-permission-node@0.10.5-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.14-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.15-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.14-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.14-next.1 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.14-next.1 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.14-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.9.1-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.6-next.1 + - @backstage/plugin-scaffolder-node@0.12.0-next.1 + - @backstage/backend-openapi-utils@0.6.2-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.13-next.1 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + - @backstage/plugin-scaffolder-common@1.7.2-next.1 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.14-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.14-next.1 + - @backstage/plugin-scaffolder-node@0.12.0-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.1 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.1 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-defaults@0.13.0-next.1 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.1 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.1 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.1 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.1 + +## @backstage/plugin-scaffolder-backend-module-github@0.9.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.1 + - @backstage/plugin-catalog-node@1.19.1-next.0 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.9.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.1 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-notifications-common@0.1.1-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.1 + - @backstage/plugin-notifications-node@0.2.20-next.0 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.2.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.1 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.1 + - @backstage/plugin-scaffolder-node-test-utils@0.3.4-next.1 + +## @backstage/plugin-scaffolder-common@1.7.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.1-next.1 + - @backstage/plugin-permission-common@0.9.2-next.0 + +## @backstage/plugin-scaffolder-node@0.12.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-scaffolder-common@1.7.2-next.1 + +## @backstage/plugin-scaffolder-node-test-utils@0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/backend-test-utils@1.9.1-next.1 + - @backstage/plugin-scaffolder-node@0.12.0-next.1 + +## @backstage/plugin-scaffolder-react@1.19.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/plugin-permission-react@0.4.37-next.0 + - @backstage/plugin-catalog-react@1.21.2-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/plugin-scaffolder-common@1.7.2-next.1 + - @backstage/catalog-client@1.12.0 + +## @backstage/plugin-search@1.4.31-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/core-compat-api@0.5.3-next.1 + - @backstage/plugin-catalog-react@1.21.2-next.1 + - @backstage/plugin-search-react@1.9.5-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/plugin-search-common@1.2.20-next.0 + +## @backstage/plugin-search-backend@2.0.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-defaults@0.13.0-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-permission-node@0.10.5-next.0 + - @backstage/plugin-search-backend-node@1.3.16-next.0 + - @backstage/backend-openapi-utils@0.6.2-next.0 + - @backstage/plugin-search-common@1.2.20-next.0 + +## @backstage/plugin-search-backend-module-catalog@0.3.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-search-backend-node@1.3.16-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + - @backstage/plugin-search-common@1.2.20-next.0 + - @backstage/catalog-client@1.12.0 + +## @backstage/plugin-search-backend-module-elasticsearch@1.7.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration-aws-node@0.1.18-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-search-backend-node@1.3.16-next.0 + - @backstage/plugin-search-common@1.2.20-next.0 + +## @backstage/plugin-search-backend-module-explore@0.3.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-search-backend-node@1.3.16-next.0 + - @backstage/plugin-search-common@1.2.20-next.0 + +## @backstage/plugin-search-backend-module-pg@0.5.49-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-search-backend-node@1.3.16-next.0 + - @backstage/plugin-search-common@1.2.20-next.0 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-search-backend-node@1.3.16-next.0 + - @backstage/plugin-search-common@1.2.20-next.0 + +## @backstage/plugin-search-backend-module-techdocs@0.4.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-search-backend-node@1.3.16-next.0 + - @backstage/plugin-techdocs-node@1.13.8-next.1 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + - @backstage/plugin-search-common@1.2.20-next.0 + - @backstage/catalog-client@1.12.0 + +## @backstage/plugin-search-backend-node@1.3.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-search-common@1.2.20-next.0 + +## @backstage/plugin-search-common@1.2.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.2-next.0 + +## @backstage/plugin-search-react@1.9.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/plugin-search-common@1.2.20-next.0 + +## @backstage/plugin-signals@0.0.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/core-compat-api@0.5.3-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/plugin-signals-react@0.0.16-next.0 + +## @backstage/plugin-signals-backend@0.3.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + - @backstage/plugin-signals-node@0.1.25-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + +## @backstage/plugin-signals-node@0.1.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + +## @backstage/plugin-signals-react@0.0.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.11.1-next.0 + +## @backstage/plugin-techdocs@1.15.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/integration-react@1.2.11-next.1 + - @backstage/plugin-techdocs-react@1.3.4-next.1 + - @backstage/core-compat-api@0.5.3-next.1 + - @backstage/plugin-catalog-react@1.21.2-next.1 + - @backstage/plugin-search-react@1.9.5-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/plugin-auth-react@0.1.20-next.1 + - @backstage/plugin-search-common@1.2.20-next.0 + - @backstage/catalog-client@1.12.0 + +## @backstage/plugin-techdocs-addons-test-utils@1.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.1-next.0 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/integration-react@1.2.11-next.1 + - @backstage/test-utils@1.7.12-next.0 + - @backstage/plugin-techdocs@1.15.1-next.1 + - @backstage/plugin-techdocs-react@1.3.4-next.1 + - @backstage/plugin-catalog@1.31.4-next.1 + - @backstage/plugin-catalog-react@1.21.2-next.1 + - @backstage/plugin-search-react@1.9.5-next.1 + +## @backstage/plugin-techdocs-backend@2.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-defaults@0.13.0-next.1 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.4.7-next.1 + - @backstage/plugin-techdocs-node@1.13.8-next.1 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + - @backstage/catalog-client@1.12.0 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.1-next.1 + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/integration-react@1.2.11-next.1 + - @backstage/plugin-techdocs-react@1.3.4-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + +## @backstage/plugin-techdocs-node@1.13.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/integration-aws-node@0.1.18-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-search-common@1.2.20-next.0 + +## @backstage/plugin-techdocs-react@1.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.1 + +## @backstage/plugin-user-settings@0.8.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.1-next.0 + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/core-compat-api@0.5.3-next.1 + - @backstage/plugin-catalog-react@1.21.2-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/plugin-signals-react@0.0.16-next.0 + +## @backstage/plugin-user-settings-backend@0.3.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.0-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + - @backstage/plugin-signals-node@0.1.25-next.0 + +## example-app@0.2.114-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/cli@0.34.4-next.1 + - @backstage/core-app-api@1.19.1-next.0 + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/frontend-app-api@0.13.1-next.1 + - @backstage/integration-react@1.2.11-next.1 + - @backstage/plugin-catalog-import@0.13.6-next.1 + - @backstage/plugin-home@0.8.13-next.1 + - @backstage/plugin-permission-react@0.4.37-next.0 + - @backstage/plugin-techdocs@1.15.1-next.1 + - @backstage/plugin-techdocs-react@1.3.4-next.1 + - @backstage/app-defaults@1.7.1-next.1 + - @backstage/plugin-api-docs@0.13.0-next.1 + - @backstage/plugin-catalog@1.31.4-next.1 + - @backstage/plugin-catalog-graph@0.5.2-next.1 + - @backstage/plugin-catalog-react@1.21.2-next.1 + - @backstage/plugin-notifications@0.5.10-next.1 + - @backstage/plugin-org@0.6.45-next.1 + - @backstage/plugin-scaffolder@1.34.2-next.1 + - @backstage/plugin-scaffolder-react@1.19.2-next.1 + - @backstage/plugin-search@1.4.31-next.1 + - @backstage/plugin-search-react@1.9.5-next.1 + - @backstage/plugin-signals@0.0.24-next.1 + - @backstage/plugin-user-settings@0.8.27-next.1 + - @backstage/plugin-auth-react@0.1.20-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.22-next.1 + - @backstage/plugin-devtools@0.1.32-next.1 + - @backstage/plugin-kubernetes@0.12.12-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.30-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.29-next.1 + - @backstage/plugin-catalog-common@1.1.6-next.0 + - @backstage/plugin-search-common@1.2.20-next.0 + +## example-app-next@0.0.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/cli@0.34.4-next.1 + - @backstage/core-app-api@1.19.1-next.0 + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/frontend-app-api@0.13.1-next.1 + - @backstage/frontend-defaults@0.3.2-next.1 + - @backstage/integration-react@1.2.11-next.1 + - @backstage/plugin-catalog-import@0.13.6-next.1 + - @backstage/plugin-home@0.8.13-next.1 + - @backstage/plugin-permission-react@0.4.37-next.0 + - @backstage/plugin-techdocs@1.15.1-next.1 + - @backstage/plugin-techdocs-react@1.3.4-next.1 + - @backstage/app-defaults@1.7.1-next.1 + - @backstage/core-compat-api@0.5.3-next.1 + - @backstage/plugin-api-docs@0.13.0-next.1 + - @backstage/plugin-catalog@1.31.4-next.1 + - @backstage/plugin-catalog-graph@0.5.2-next.1 + - @backstage/plugin-catalog-react@1.21.2-next.1 + - @backstage/plugin-notifications@0.5.10-next.1 + - @backstage/plugin-org@0.6.45-next.1 + - @backstage/plugin-scaffolder@1.34.2-next.1 + - @backstage/plugin-scaffolder-react@1.19.2-next.1 + - @backstage/plugin-search@1.4.31-next.1 + - @backstage/plugin-search-react@1.9.5-next.1 + - @backstage/plugin-signals@0.0.24-next.1 + - @backstage/plugin-user-settings@0.8.27-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/plugin-app@0.3.1-next.1 + - @backstage/plugin-app-visualizer@0.1.24-next.1 + - @backstage/plugin-auth-react@0.1.20-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.22-next.1 + - @backstage/plugin-kubernetes@0.12.12-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.30-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.29-next.1 + - @backstage/plugin-auth@0.1.1-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + - @backstage/plugin-search-common@1.2.20-next.0 + +## example-backend@0.0.43-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.0-next.1 + - @backstage/plugin-app-backend@0.5.7-next.0 + - @backstage/plugin-devtools-backend@0.5.10-next.1 + - @backstage/plugin-proxy-backend@0.6.7-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-backend@0.25.5-next.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.13-next.0 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.1-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + - @backstage/plugin-catalog-backend@3.1.1-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.7-next.0 + - @backstage/plugin-events-backend@0.5.7-next.0 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.5-next.0 + - @backstage/plugin-kubernetes-backend@0.20.3-next.1 + - @backstage/plugin-notifications-backend@0.5.11-next.0 + - @backstage/plugin-permission-backend@0.7.5-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-permission-node@0.10.5-next.0 + - @backstage/plugin-scaffolder-backend@3.0.0-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.9.1-next.1 + - @backstage/plugin-search-backend@2.0.7-next.1 + - @backstage/plugin-search-backend-module-catalog@0.3.9-next.0 + - @backstage/plugin-search-backend-module-explore@0.3.8-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.4.7-next.1 + - @backstage/plugin-search-backend-node@1.3.16-next.0 + - @backstage/plugin-signals-backend@0.3.9-next.0 + - @backstage/plugin-techdocs-backend@2.1.1-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.3.8-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.2.15-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.13-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.5-next.0 + - @backstage/plugin-mcp-actions-backend@0.1.4-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.13-next.0 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.15-next.1 + +## techdocs-cli-embedded-app@0.2.113-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/cli@0.34.4-next.1 + - @backstage/core-app-api@1.19.1-next.0 + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/integration-react@1.2.11-next.1 + - @backstage/test-utils@1.7.12-next.0 + - @backstage/plugin-techdocs@1.15.1-next.1 + - @backstage/plugin-techdocs-react@1.3.4-next.1 + - @backstage/app-defaults@1.7.1-next.1 + - @backstage/plugin-catalog@1.31.4-next.1 + +## @internal/plugin-todo-list@1.0.44-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + +## @internal/plugin-todo-list-backend@1.0.44-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + +## @internal/plugin-todo-list-common@1.0.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.2-next.0 diff --git a/docs/releases/v1.44.0-next.2-changelog.md b/docs/releases/v1.44.0-next.2-changelog.md new file mode 100644 index 0000000000..74b467ebba --- /dev/null +++ b/docs/releases/v1.44.0-next.2-changelog.md @@ -0,0 +1,2298 @@ +# Release v1.44.0-next.2 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.44.0-next.2](https://backstage.github.io/upgrade-helper/?to=1.44.0-next.2) + +## @backstage/config@1.3.5-next.0 + +# @backstage/config + +## 1.3.4-next.0 + +### Patch Changes + +- b45b094: Allow colon to be used as config key. + +## 1.3.3 + +### Patch Changes + +- ff23618: Loosen the requirements for a key to be considered valid config. +- 3507fcd: Just some more circular dep cleanup + +## 1.3.3-next.0 + +### Patch Changes + +- ff23618: Loosen the requirements for a key to be considered valid config. +- 3507fcd: Just some more circular dep cleanup + +## 1.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.2.1 + - @backstage/errors@1.2.7 + +## 1.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.2.1-next.0 + - @backstage/errors@1.2.7-next.0 + +## 1.3.1 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.6 + - @backstage/types@1.2.0 + +## 1.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.6-next.0 + - @backstage/types@1.2.0 + +## 1.3.0 + +### Minor Changes + +- d52d7f9: Make `readDurationFromConfig` support both ISO and ms formats as well, to make it easier to enter time as an end user + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.2.0 + - @backstage/errors@1.2.5 + +## 1.2.0 + +### Minor Changes + +- 50cf9df: The `ConfigReader` now treats `null` values as present but explicitly undefined, meaning it will not fall back to the next level of configuration. + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.2.0-next.1 + +### Minor Changes + +- 50cf9df: The `ConfigReader` now treats `null` values as present but explicitly undefined, meaning it will not fall back to the next level of configuration. + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.4-next.0 + - @backstage/types@1.1.1 + +## 1.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.4-next.0 + - @backstage/types@1.1.1 + +## 1.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 1.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.3-next.0 + - @backstage/types@1.1.1 + +## 1.1.0 + +### Minor Changes + +- 62f448edb0b5: Added a `readDurationFromConfig` function + +### Patch Changes + +- 406b786a2a2c: Mark package as being free of side effects, allowing more optimized Webpack builds. +- 8cec7664e146: Removed `@types/node` dependency +- Updated dependencies + - @backstage/errors@1.2.2 + - @backstage/types@1.1.1 + +## 1.1.0-next.2 + +### Patch Changes + +- 406b786a2a2c: Mark package as being free of side effects, allowing more optimized Webpack builds. +- Updated dependencies + - @backstage/errors@1.2.2-next.0 + - @backstage/types@1.1.1-next.0 + +## 1.1.0-next.1 + +### Patch Changes + +- 8cec7664e146: Removed `@types/node` dependency +- Updated dependencies + - @backstage/errors@1.2.1 + - @backstage/types@1.1.0 + +## 1.1.0-next.0 + +### Minor Changes + +- 62f448edb0b5: Added a `readDurationFromConfig` function + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.1 + - @backstage/types@1.1.0 + +## 1.0.8 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.1.0 + +## 1.0.7 + +### Patch Changes + +- 482dae5de1c: Updated link to docs. +- Updated dependencies + - @backstage/types@1.0.2 + +## 1.0.7-next.0 + +### Patch Changes + +- 482dae5de1c: Updated link to docs. +- Updated dependencies + - @backstage/types@1.0.2 + +## 1.0.6 + +### Patch Changes + +- ba2d69ee17: Adds the ability to coerce values to their boolean representatives. + Values such as `"true"` `1` `on` and `y` will become `true` when using `getBoolean` and the opposites `false`. + This happens particularly when such parameters are used with environmental substitution as environment variables are always strings. +- Updated dependencies + - @backstage/types@1.0.2 + +## 1.0.6-next.0 + +### Patch Changes + +- ba2d69ee17: Adds the ability to coerce values to their boolean representatives. + Values such as `"true"` `1` `on` and `y` will become `true` when using `getBoolean` and the opposites `false`. + This happens particularly when such parameters are used with environmental substitution as environment variables are always strings. +- Updated dependencies + - @backstage/types@1.0.2 + +## 1.0.5 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.2 + +## 1.0.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.2-next.1 + +## 1.0.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.2-next.0 + +## 1.0.4 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.1 + +## 1.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.1-next.0 + +## 1.0.3 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## 1.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## 1.0.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## 1.0.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## 1.0.2 + +### Patch Changes + +- 7d47def9c4: Removed dependency on `@types/jest`. + +## 1.0.2-next.0 + +### Patch Changes + +- 7d47def9c4: Removed dependency on `@types/jest`. + +## 1.0.1 + +### Patch Changes + +- 6e830352d4: Updated dependency `@types/node` to `^16.0.0`. + +## 1.0.1-next.0 + +### Patch Changes + +- 6e830352d4: Updated dependency `@types/node` to `^16.0.0`. + +## 1.0.0 + +### Major Changes + +- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## 0.1.15 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/types@0.1.3 + +## 0.1.14 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/types@0.1.2 + +## 0.1.13 + +### Patch Changes + +- f685e1398f: Loading of app configurations now reference the `@deprecated` construct from + JSDoc to determine if a property in-use has been deprecated. Users are notified + of deprecated keys in the format: + + ```txt + The configuration key 'catalog.processors.githubOrg' of app-config.yaml is deprecated and may be removed soon. Configure a GitHub integration instead. + ``` + + When the `withDeprecatedKeys` option is set to `true` in the `process` method + of `loadConfigSchema`, the user will be notified that deprecated keys have been + identified in their app configuration. + + The `backend-common` and `plugin-app-backend` packages have been updated to set + `withDeprecatedKeys` to true so that users are notified of deprecated settings + by default. + +## 0.1.13-next.0 + +### Patch Changes + +- f685e1398f: Loading of app configurations now reference the `@deprecated` construct from + JSDoc to determine if a property in-use has been deprecated. Users are notified + of deprecated keys in the format: + + ```txt + The configuration key 'catalog.processors.githubOrg' of app-config.yaml is deprecated and may be removed soon. Configure a GitHub integration instead. + ``` + + When the `withDeprecatedKeys` option is set to `true` in the `process` method + of `loadConfigSchema`, the user will be notified that deprecated keys have been + identified in their app configuration. + + The `backend-common` and `plugin-app-backend` packages have been updated to set + `withDeprecatedKeys` to true so that users are notified of deprecated settings + by default. + +## 0.1.12 + +### Patch Changes + +- f5343e7c1a: The `ConfigReader#get` method now always returns a deep clone of the configuration data. + +## 0.1.11 + +### Patch Changes + +- 10d267a1b7: Minor exports cleanup +- 41c49884d2: Start using the new `@backstage/types` package. Initially, this means using the `Observable` and `Json*` types from there. The types also remain in their old places but deprecated, and will be removed in a future release. +- 925a967f36: Replace usage of test-utils-core with test-utils + +## 0.1.10 + +### Patch Changes + +- febddedcb2: Bump `lodash` to remediate `SNYK-JS-LODASH-590103` security vulnerability + +## 0.1.9 + +### Patch Changes + +- f88b2c7db: Documented `Config` interface and mark types as public. + +## 0.1.8 + +### Patch Changes + +- 47113f1f1: Only warn once per key when trying to read visibility-filtered values + +## 0.1.7 + +### Patch Changes + +- 90f25476a: Extended the `Config` interface to have an optional `subscribe` method that can be used be notified of updates to the configuration. + +## 0.1.6 + +### Patch Changes + +- e9d3983ee: Add warning when trying to access configuration values that have been filtered out by visibility. + +## 0.1.5 + +### Patch Changes + +- d8b81fd28: Bump `json-schema` dependency from `0.2.5` to `0.3.0`. + +## 0.1.4 + +### Patch Changes + +- 0434853a5: Reformulate the json types to break type recursion + +## 0.1.3 + +### Patch Changes + +- a1f5e6545: Adds an optional type to `config.get` & `config.getOptional`. This avoids the need for casting. For example: + + ```ts + const config = useApi(configApiRef); + + const myConfig = config.get('myPlugin.complexConfig'); + // vs + const myConfig config.get('myPlugin.complexConfig') as SomeTypeDefinition; + ``` + +## 0.1.2 + +### Patch Changes + +- e3bd9fc2f: Fix unneeded defensive code +- e3bd9fc2f: Fix useless conditional + +## @backstage/config-loader@1.10.5-next.0 + +# @backstage/config-loader + +## 1.10.4-next.0 + +### Patch Changes + +- b45b094: Allow colon to be used as config key. +- Updated dependencies + - @backstage/config@1.3.4-next.0 + +## 1.10.3 + +### Patch Changes + +- a73f495: Allow using `BACKSTAGE_ENV` for loading environment specific config files +- Updated dependencies + - @backstage/types@1.2.2 + +## 1.10.3-next.0 + +### Patch Changes + +- a73f495: Allow using `BACKSTAGE_ENV` for loading environment specific config files + +## 1.10.2 + +### Patch Changes + +- ff23618: Loosen the requirements for a key to be considered valid config. +- Updated dependencies + - @backstage/config@1.3.3 + +## 1.10.2-next.0 + +### Patch Changes + +- ff23618: Loosen the requirements for a key to be considered valid config. +- Updated dependencies + - @backstage/config@1.3.3-next.0 + +## 1.10.1 + +### Patch Changes + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 1.10.1-next.0 + +### Patch Changes + +- 72d019d: Removed various typos +- Updated dependencies + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 1.10.0 + +### Minor Changes + +- 2fd73aa: The include transforms applied during config loading will now only apply to the known keys `$file`, `$env`, and `$include`. Any other key that begins with a \`# @backstage/config-loader will now be passed through as is. + +### Patch Changes + +- f422984: Added `@types/minimist` to `devDependencies` +- Updated dependencies + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 1.10.0-next.0 + +### Minor Changes + +- 2fd73aa: The include transforms applied during config loading will now only apply to the known keys `$file`, `$env`, and `$include`. Any other key that begins with a \`# @backstage/config-loader will now be passed through as is. + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 1.9.6 + +### Patch Changes + +- f866b86: Internal refactor to use explicit `require` for lazy-loading dependency. +- Updated dependencies + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 1.9.6-next.0 + +### Patch Changes + +- f866b86: Internal refactor to use explicit `require` for lazy-loading dependency. +- Updated dependencies + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 1.9.5 + +### Patch Changes + +- 8ecf8cb: Exclude `@backstage/backend-common` from schema collection if `@backstage/backend-defaults` is present +- Updated dependencies + - @backstage/types@1.2.1 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 1.9.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.2.1-next.0 + - @backstage/config@1.3.2-next.0 + - @backstage/errors@1.2.7-next.0 + - @backstage/cli-common@0.1.15 + +## 1.9.5-next.0 + +### Patch Changes + +- 8ecf8cb: Exclude `@backstage/backend-common` from schema collection if `@backstage/backend-defaults` is present +- Updated dependencies + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.1 + - @backstage/errors@1.2.6 + - @backstage/types@1.2.0 + +## 1.9.3 + +### Patch Changes + +- 5c9cc05: Use native fetch instead of node-fetch +- Updated dependencies + - @backstage/errors@1.2.6 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.1 + - @backstage/types@1.2.0 + +## 1.9.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.6-next.0 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.1-next.0 + - @backstage/types@1.2.0 + +## 1.9.3-next.0 + +### Patch Changes + +- 5c9cc05: Use native fetch instead of node-fetch +- Updated dependencies + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.0 + - @backstage/errors@1.2.5 + - @backstage/types@1.2.0 + +## 1.9.2 + +### Patch Changes + +- c5e39e7: Internal refactor to use the deferred from the types package +- Updated dependencies + - @backstage/config@1.3.0 + - @backstage/types@1.2.0 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.5 + +## 1.9.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.15-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.9.1 + +### Patch Changes + +- ef3c507: Updated dependency `typescript-json-schema` to `^0.65.0`. +- Updated dependencies + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.9.1-next.0 + +### Patch Changes + +- ef3c507: Updated dependency `typescript-json-schema` to `^0.65.0`. +- Updated dependencies + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.9.0 + +### Minor Changes + +- 274428f: Add configuration key to File and Remote `ConfigSource`s that enables configuration of parsing logic. Previously limited to yaml, these `ConfigSource`s now allow for a multitude of parsing options (e.g. JSON). + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 1edd6c2: The `env` option of `ConfigSources.default` now correctly allows undefined members. +- 493feac: Add boolean `allowMissingDefaultConfig` option to `ConfigSources.default` and + `ConfigSources.defaultForTargets`, which results in omission of a ConfigSource + for the default app-config.yaml configuration file if it's not present. +- Updated dependencies + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.9.0-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.9.0-next.1 + +### Minor Changes + +- 274428f: Add configuration key to File and Remote `ConfigSource`s that enables configuration of parsing logic. Previously limited to yaml, these `ConfigSource`s now allow for a multitude of parsing options (e.g. JSON). + +### Patch Changes + +- 1edd6c2: The `env` option of `ConfigSources.default` now correctly allows undefined members. +- Updated dependencies + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.8.2-next.0 + +### Patch Changes + +- 493feac: Add boolean `allowMissingDefaultConfig` option to `ConfigSources.default` and + `ConfigSources.defaultForTargets`, which results in omission of a ConfigSource + for the default app-config.yaml configuration file if it's not present. +- Updated dependencies + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.8.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.8.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.14-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.8.0 + +### Minor Changes + +- 2ce31b3: The default environment variable substitution function will now trim whitespace characters from the substituted value. This alleviates bugs where whitespace characters are mistakenly included in environment variables. + + If you depend on the old behavior, you can override the default substitution function with your own, for example: + + ```ts + ConfigSources.default({ + substitutionFunc: async name => process.env[name], + }); + ``` + +- 99bab65: Support parameter substitution for environment variables + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.8.0-next.0 + +### Minor Changes + +- 99bab65: Support parameter substitution for environment variables + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.7.0 + +### Minor Changes + +- db8358d: Forward `null` values read from configuration files in configuration data, rather than treating them as an absence of config. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/cli-common@0.1.13 + - @backstage/types@1.1.1 + +## 1.7.0-next.1 + +### Minor Changes + +- db8358d: Forward `null` values read from configuration files in configuration data, rather than treating them as an absence of config. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.2.0-next.1 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.4-next.0 + - @backstage/types@1.1.1 + +## 1.6.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.4-next.0 + - @backstage/config@1.1.2-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/types@1.1.1 + +## 1.6.2 + +### Patch Changes + +- 0a9a03c: Make schema processing gracefully handle an empty config. +- 6bb6f3e: Updated dependency `fs-extra` to `^11.2.0`. + Updated dependency `@types/fs-extra` to `^11.0.0`. +- bf3da16: Updated dependency `typescript-json-schema` to `^0.63.0`. +- Updated dependencies + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 1.6.2-next.0 + +### Patch Changes + +- 0a9a03c: Make schema processing gracefully handle an empty config. +- Updated dependencies + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 1.6.1 + +### Patch Changes + +- 7acbb5a: Removed `mock-fs` dev dependency. +- Updated dependencies + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 1.6.1-next.0 + +### Patch Changes + +- 7acbb5a: Removed `mock-fs` dev dependency. +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 1.6.0 + +### Minor Changes + +- 24f5a85: Add "path" to `TransformFunc` context + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 1.6.0-next.0 + +### Minor Changes + +- 24f5a85: Add "path" to `TransformFunc` context + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 1.5.3 + +### Patch Changes + +- 22ca64f117: Correctly resolve config targets into absolute paths +- 087bab5b42: Updated dependency `typescript-json-schema` to `^0.62.0`. +- Updated dependencies + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 1.5.2-next.0 + +### Patch Changes + +- 22ca64f117: Correctly resolve config targets into absolute paths +- Updated dependencies + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 1.5.1 + +### Patch Changes + +- 0b55f773a7: Removed some unused dependencies +- 30c553c1d2: Updated dependency `typescript-json-schema` to `^0.61.0`. +- 773ea341d2: The `FileConfigSource` will now retry file reading after a short delay if it reads an empty file. This is to avoid flakiness during watch mode where change events can trigger before the file content has been written. +- a4617c422a: Added `watch` option to configuration loaders that can be used to disable file watching by setting it to `false`. +- Updated dependencies + - @backstage/errors@1.2.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + +## 1.5.1-next.1 + +### Patch Changes + +- 0b55f773a7: Removed some unused dependencies +- 30c553c1d2: Updated dependency `typescript-json-schema` to `^0.61.0`. +- a4617c422a: Added `watch` option to configuration loaders that can be used to disable file watching by setting it to `false`. +- Updated dependencies + - @backstage/errors@1.2.3-next.0 + - @backstage/cli-common@0.1.13-next.0 + - @backstage/config@1.1.1-next.0 + - @backstage/types@1.1.1 + +## 1.5.1-next.0 + +### Patch Changes + +- 773ea341d2: The `FileConfigSource` will now retry file reading after a short delay if it reads an empty file. This is to avoid flakiness during watch mode where change events can trigger before the file content has been written. +- Updated dependencies + - @backstage/cli-common@0.1.13-next.0 + - @backstage/config@1.1.0 + - @backstage/errors@1.2.2 + - @backstage/types@1.1.1 + +## 1.5.0 + +### Minor Changes + +- 9606ba0939e6: Deep visibility now also applies to values that are not covered by the configuration schema. + + For example, given the following configuration schema: + + ```ts + // plugins/a/config.schema.ts + export interface Config { + /** @deepVisibility frontend */ + a?: unknown; + } + + // plugins/a/config.schema.ts + export interface Config { + a?: { + b?: string; + }; + } + ``` + + All values under `a` are now visible to the frontend, while previously only `a` and `a/b` would've been visible. + +### Patch Changes + +- 8cec7664e146: Removed `@types/node` dependency +- f9657b891b00: Do not unnecessarily notify subscribers when no-op updates to config happen +- Updated dependencies + - @backstage/config@1.1.0 + - @backstage/errors@1.2.2 + - @backstage/types@1.1.1 + - @backstage/cli-common@0.1.12 + +## 1.5.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.0-next.2 + - @backstage/errors@1.2.2-next.0 + - @backstage/types@1.1.1-next.0 + - @backstage/cli-common@0.1.12 + +## 1.5.0-next.2 + +### Patch Changes + +- 8cec7664e146: Removed `@types/node` dependency +- Updated dependencies + - @backstage/config@1.1.0-next.1 + - @backstage/cli-common@0.1.12 + - @backstage/errors@1.2.1 + - @backstage/types@1.1.0 + +## 1.5.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.0-next.0 + - @backstage/cli-common@0.1.12 + - @backstage/errors@1.2.1 + - @backstage/types@1.1.0 + +## 1.5.0-next.0 + +### Minor Changes + +- 9606ba0939e6: Deep visibility now also applies to values that are not covered by the configuration schema. + + For example, given the following configuration schema: + + ```ts + // plugins/a/config.schema.ts + export interface Config { + /** @deepVisibility frontend */ + a?: unknown; + } + + // plugins/a/config.schema.ts + export interface Config { + a?: { + b?: string; + }; + } + ``` + + All values under `a` are now visible to the frontend, while previously only `a` and `a/b` would've been visible. + +### Patch Changes + +- f9657b891b00: Do not unnecessarily notify subscribers when no-op updates to config happen +- Updated dependencies + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.8 + - @backstage/errors@1.2.1 + - @backstage/types@1.1.0 + +## 1.4.0 + +### Minor Changes + +- 2f1859585998: Loading invalid TypeScript configuration schemas will now throw an error rather than silently being ignored. + + In particular this includes defining any additional types other than `Config` in the schema file, or use of unsupported types such as `Record` or `Partial`. + +- cd514545d1d0: Adds a new `deepVisibility` schema keyword that sets child visibility recursively to the defined value, respecting preexisting values or child `deepVisibility`. + + Example usage: + + ```ts + export interface Config { + /** + * Enforces a default of `secret` instead of `backend` for this object. + * @deepVisibility secret + */ + mySecretProperty: { + type: 'object'; + properties: { + secretValue: { + type: 'string'; + }; + + verySecretProperty: { + type: 'string'; + }; + }; + }; + } + ``` + + Example of a schema that would not be allowed: + + ```ts + export interface Config { + /** + * Set the top level property to secret, enforcing a default of `secret` instead of `backend` for this object. + * @deepVisibility secret + */ + mySecretProperty: { + type: 'object'; + properties: { + frontendUrl: { + /** + * We can NOT override the visibility to reveal a property to the front end. + * @visibility frontend + */ + type: 'string'; + }; + + verySecretProperty: { + type: 'string'; + }; + }; + }; + } + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.8 + - @backstage/errors@1.2.1 + - @backstage/types@1.1.0 + +## 1.4.0-next.1 + +### Minor Changes + +- 2f1859585998: Loading invalid TypeScript configuration schemas will now throw an error rather than silently being ignored. + + In particular this includes defining any additional types other than `Config` in the schema file, or use of unsupported types such as `Record` or `Partial`. + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.8 + - @backstage/errors@1.2.1 + - @backstage/types@1.1.0 + +## 1.4.0-next.0 + +### Minor Changes + +- cd514545d1d0: Adds a new `deepVisibility` schema keyword that sets child visibility recursively to the defined value, respecting preexisting values or child `deepVisibility`. + + Example usage: + + ```ts + export interface Config { + /** + * Enforces a default of `secret` instead of `backend` for this object. + * @deepVisibility secret + */ + mySecretProperty: { + type: 'object'; + properties: { + secretValue: { + type: 'string'; + }; + + verySecretProperty: { + type: 'string'; + }; + }; + }; + } + ``` + + Example of a schema that would not be allowed: + + ```ts + export interface Config { + /** + * Set the top level property to secret, enforcing a default of `secret` instead of `backend` for this object. + * @deepVisibility secret + */ + mySecretProperty: { + type: 'object'; + properties: { + frontendUrl: { + /** + * We can NOT override the visibility to reveal a property to the front end. + * @visibility frontend + */ + type: 'string'; + }; + + verySecretProperty: { + type: 'string'; + }; + }; + }; + } + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.8 + - @backstage/errors@1.2.1 + - @backstage/types@1.1.0 + +## 1.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.1 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.8 + - @backstage/types@1.1.0 + +## 1.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.1-next.0 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.8 + - @backstage/types@1.1.0 + +## 1.3.1 + +### Patch Changes + +- f25427f665f7: Fix a bug where config items with `/` in the key were incorrectly handled. +- a5c5491ff50c: Use `durationToMilliseconds` from `@backstage/types` instead of our own +- Updated dependencies + - @backstage/types@1.1.0 + - @backstage/errors@1.2.0 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.8 + +## 1.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.0-next.0 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.7 + - @backstage/types@1.0.2 + +## 1.3.1-next.0 + +### Patch Changes + +- f25427f665f7: Fix a bug where config items with `/` in the key were incorrectly handled. +- Updated dependencies + - @backstage/config@1.0.7 + - @backstage/cli-common@0.1.12 + - @backstage/errors@1.1.5 + - @backstage/types@1.0.2 + +## 1.3.0 + +### Minor Changes + +- 201206132da: Introduced a new config source system to replace `loadConfig`. There is a new `ConfigSource` interface along with utilities provided by `ConfigSources`, as well as a number of built-in configuration source implementations. The new system is more flexible and makes it easier to create new and reusable sources of configuration, such as loading configuration from secret providers. + + The following is an example of how to load configuration using the default behavior: + + ```ts + const source = ConfigSources.default({ + argv: options?.argv, + remote: options?.remote, + }); + const config = await ConfigSources.toConfig(source); + ``` + + The `ConfigSource` interface looks like this: + + ```ts + export interface ConfigSource { + readConfigData(options?: ReadConfigDataOptions): AsyncConfigSourceIterator; + } + ``` + + It is best implemented using an async iterator: + + ```ts + class MyConfigSource implements ConfigSource { + async *readConfigData() { + yield { + config: [ + { + context: 'example', + data: { backend: { baseUrl: 'http://localhost' } }, + }, + ], + }; + } + } + ``` + +### Patch Changes + +- 7c116bcac7f: Fixed the way that some request errors are thrown +- 473db605a4f: Added a new `noUndeclaredProperties` option to `SchemaLoader` to support enforcing that there are no extra keys when verifying config. +- Updated dependencies + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + - @backstage/types@1.0.2 + +## 1.3.0-next.0 + +### Minor Changes + +- 201206132da: Introduced a new config source system to replace `loadConfig`. There is a new `ConfigSource` interface along with utilities provided by `ConfigSources`, as well as a number of built-in configuration source implementations. The new system is more flexible and makes it easier to create new and reusable sources of configuration, such as loading configuration from secret providers. + + The following is an example of how to load configuration using the default behavior: + + ```ts + const source = ConfigSources.default({ + argv: options?.argv, + remote: options?.remote, + }); + const config = await ConfigSources.toConfig(source); + ``` + + The `ConfigSource` interface looks like this: + + ```ts + export interface ConfigSource { + readConfigData(options?: ReadConfigDataOptions): AsyncConfigSourceIterator; + } + ``` + + It is best implemented using an async iterator: + + ```ts + class MyConfigSource implements ConfigSource { + async *readConfigData() { + yield { + config: [ + { + context: 'example', + data: { backend: { baseUrl: 'http://localhost' } }, + }, + ], + }; + } + } + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + - @backstage/types@1.0.2 + +## 1.2.0 + +### Minor Changes + +- c791fcd96b9: Configuration validation is now more permissive when it comes to config whose values are `string` but whose schemas declare them to be `boolean` or `number`. + + For example, configuration was previously marked invalid when a string `'true'` was set on a property expecting type `boolean` or a string `'146'` was set on a property expecting type `number` (as when providing configuration via variable substitution sourced from environment variables). Now, such configurations will be considered valid and their values will be coerced to the right type at read-time. + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + - @backstage/types@1.0.2 + +## 1.1.9 + +### Patch Changes + +- 52b0022dab7: Updated dependency `msw` to `^1.0.0`. +- 482dae5de1c: Updated link to docs. +- Updated dependencies + - @backstage/errors@1.1.5 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.7 + - @backstage/types@1.0.2 + +## 1.1.9-next.0 + +### Patch Changes + +- 52b0022dab7: Updated dependency `msw` to `^1.0.0`. +- 482dae5de1c: Updated link to docs. +- Updated dependencies + - @backstage/errors@1.1.5-next.0 + - @backstage/cli-common@0.1.12-next.0 + - @backstage/config@1.0.7-next.0 + - @backstage/types@1.0.2 + +## 1.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.6 + - @backstage/cli-common@0.1.11 + - @backstage/errors@1.1.4 + - @backstage/types@1.0.2 + +## 1.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.6-next.0 + - @backstage/cli-common@0.1.11 + - @backstage/errors@1.1.4 + - @backstage/types@1.0.2 + +## 1.1.7 + +### Patch Changes + +- 3280711113: Updated dependency `msw` to `^0.49.0`. +- 40e7e6e1a2: Updated dependency `typescript-json-schema` to `^0.55.0`. +- Updated dependencies + - @backstage/errors@1.1.4 + - @backstage/types@1.0.2 + - @backstage/cli-common@0.1.11 + - @backstage/config@1.0.5 + +## 1.1.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.11-next.0 + - @backstage/config@1.0.5-next.1 + - @backstage/errors@1.1.4-next.1 + - @backstage/types@1.0.2-next.1 + +## 1.1.7-next.1 + +### Patch Changes + +- 40e7e6e1a2: Updated dependency `typescript-json-schema` to `^0.55.0`. +- Updated dependencies + - @backstage/types@1.0.2-next.1 + - @backstage/config@1.0.5-next.1 + - @backstage/cli-common@0.1.10 + - @backstage/errors@1.1.4-next.1 + +## 1.1.7-next.0 + +### Patch Changes + +- 3280711113: Updated dependency `msw` to `^0.49.0`. +- Updated dependencies + - @backstage/types@1.0.2-next.0 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.5-next.0 + - @backstage/errors@1.1.4-next.0 + +## 1.1.6 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.1 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.4 + - @backstage/errors@1.1.3 + +## 1.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.1-next.0 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 1.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## 1.1.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## 1.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/types@1.0.0 + +## 1.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/types@1.0.0 + +## 1.1.4 + +### Patch Changes + +- 5ecca7e44b: No longer log when reloading remote config. +- 7d47def9c4: Removed dependency on `@types/jest`. +- 667d917488: Updated dependency `msw` to `^0.47.0`. +- 87ec2ba4d6: Updated dependency `msw` to `^0.46.0`. +- bf5e9030eb: Updated dependency `msw` to `^0.45.0`. +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.2 + - @backstage/errors@1.1.1 + +## 1.1.4-next.2 + +### Patch Changes + +- 5ecca7e44b: No longer log when reloading remote config. +- 7d47def9c4: Removed dependency on `@types/jest`. +- Updated dependencies + - @backstage/cli-common@0.1.10-next.0 + - @backstage/config@1.0.2-next.0 + - @backstage/errors@1.1.1-next.0 + +## 1.1.4-next.1 + +### Patch Changes + +- 667d917488: Updated dependency `msw` to `^0.47.0`. +- 87ec2ba4d6: Updated dependency `msw` to `^0.46.0`. + +## 1.1.4-next.0 + +### Patch Changes + +- bf5e9030eb: Updated dependency `msw` to `^0.45.0`. + +## 1.1.3 + +### Patch Changes + +- bcada7cd9f: From now on the `$file` placeholder will trim the whitespaces and newline characters from the end of the file it reads. +- a70869e775: Updated dependency `msw` to `^0.43.0`. +- 72622d9143: Updated dependency `yaml` to `^2.0.0`. +- 8006d0f9bf: Updated dependency `msw` to `^0.44.0`. +- a3acec8819: Updated dependency `typescript-json-schema` to `^0.54.0`. +- Updated dependencies + - @backstage/errors@1.1.0 + +## 1.1.3-next.1 + +### Patch Changes + +- a70869e775: Updated dependency `msw` to `^0.43.0`. +- 72622d9143: Updated dependency `yaml` to `^2.0.0`. +- a3acec8819: Updated dependency `typescript-json-schema` to `^0.54.0`. + +## 1.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.1.0-next.0 + +## 1.1.2 + +### Patch Changes + +- 8f7b1835df: Updated dependency `msw` to `^0.41.0`. + +## 1.1.2-next.0 + +### Patch Changes + +- 8f7b1835df: Updated dependency `msw` to `^0.41.0`. + +## 1.1.1 + +### Patch Changes + +- cfc0f19699: Updated dependency `fs-extra` to `10.1.0`. +- 9e8ef53498: Handle empty config files gracefully +- Updated dependencies + - @backstage/cli-common@0.1.9 + - @backstage/config@1.0.1 + +## 1.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.9-next.0 + - @backstage/config@1.0.1-next.0 + +## 1.1.1-next.0 + +### Patch Changes + +- cfc0f19699: Updated dependency `fs-extra` to `10.1.0`. +- 9e8ef53498: Handle empty config files gracefully + +## 1.1.0 + +### Minor Changes + +- 19f6c6c32a: Added `ignoreSchemaErrors` to `schema.process`. + +### Patch Changes + +- e0a51384ac: build(deps): bump `ajv` from 7.0.3 to 8.10.0 +- 230ad0826f: Bump to using `@types/node` v16 +- c47509e1a0: Implemented changes suggested by Deepsource.io including multiple double non-null assertion operators and unexpected awaits for non-promise values. + +## 1.1.0-next.1 + +### Minor Changes + +- 19f6c6c32a: Added `ignoreSchemaErrors` to `schema.process`. + +### Patch Changes + +- 230ad0826f: Bump to using `@types/node` v16 + +## 1.0.1-next.0 + +### Patch Changes + +- e0a51384ac: build(deps): bump `ajv` from 7.0.3 to 8.10.0 +- c47509e1a0: Implemented changes suggested by Deepsource.io including multiple double non-null assertion operators and unexpected awaits for non-promise values. + +## 1.0.0 + +### Major Changes + +- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +### Patch Changes + +- 664821371e: The `typescript-json-schema` dependency that is used during schema collection is now lazy loaded, as it eagerly loads in the TypeScript compiler. +- f910c2a3f8: build(deps): bump `typescript-json-schema` from 0.52.0 to 0.53.0 +- Updated dependencies + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/types@1.0.0 + +## 0.9.7 + +### Patch Changes + +- e0a69ba49f: build(deps): bump `fs-extra` from 9.1.0 to 10.0.1 + +## 0.9.7-next.0 + +### Patch Changes + +- e0a69ba49f: build(deps): bump `fs-extra` from 9.1.0 to 10.0.1 + +## 0.9.6 + +### Patch Changes + +- c3a1300f79: Include any files included in configuration via $include or $file directives when watching for configuration changes. + +## 0.9.5 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/cli-common@0.1.8 + - @backstage/config@0.1.15 + - @backstage/errors@0.2.2 + - @backstage/types@0.1.3 + +## 0.9.4 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/errors@0.2.1 + - @backstage/cli-common@0.1.7 + - @backstage/config@0.1.14 + - @backstage/types@0.1.2 + +## 0.9.3 + +### Patch Changes + +- f685e1398f: Loading of app configurations now reference the `@deprecated` construct from + JSDoc to determine if a property in-use has been deprecated. Users are notified + of deprecated keys in the format: + + ```txt + The configuration key 'catalog.processors.githubOrg' of app-config.yaml is deprecated and may be removed soon. Configure a GitHub integration instead. + ``` + + When the `withDeprecatedKeys` option is set to `true` in the `process` method + of `loadConfigSchema`, the user will be notified that deprecated keys have been + identified in their app configuration. + + The `backend-common` and `plugin-app-backend` packages have been updated to set + `withDeprecatedKeys` to true so that users are notified of deprecated settings + by default. + +- Updated dependencies + - @backstage/config@0.1.13 + +## 0.9.3-next.0 + +### Patch Changes + +- f685e1398f: Loading of app configurations now reference the `@deprecated` construct from + JSDoc to determine if a property in-use has been deprecated. Users are notified + of deprecated keys in the format: + + ```txt + The configuration key 'catalog.processors.githubOrg' of app-config.yaml is deprecated and may be removed soon. Configure a GitHub integration instead. + ``` + + When the `withDeprecatedKeys` option is set to `true` in the `process` method + of `loadConfigSchema`, the user will be notified that deprecated keys have been + identified in their app configuration. + + The `backend-common` and `plugin-app-backend` packages have been updated to set + `withDeprecatedKeys` to true so that users are notified of deprecated settings + by default. + +- Updated dependencies + - @backstage/config@0.1.13-next.0 + +## 0.9.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@0.1.12 + - @backstage/errors@0.2.0 + +## 0.9.1 + +### Patch Changes + +- 84663d59a3: Bump `typescript-json-schema` from `^0.51.0` to `^0.52.0`. + +## 0.9.0 + +### Minor Changes + +- f6722d2458: Removed deprecated option `env` from `LoadConfigOptions` and associated tests +- 67d6cb3c7e: Removed deprecated option `configPaths` as it has been superseded by `configTargets` + +### Patch Changes + +- 1e7070443d: In case remote.reloadIntervalSeconds is passed, it must be a valid positive value + +## 0.8.1 + +### Patch Changes + +- b055a6addc: Align on usage of `cross-fetch` vs `node-fetch` in frontend vs backend packages, and remove some unnecessary imports of either one of them +- 4bea7b81d3: Uses key visibility as fallback in non-object arrays + +## 0.8.0 + +### Minor Changes + +- 1e99c73c75: Update `loadConfig` to return `LoadConfigResult` instead of an array of `AppConfig`. + + This function is primarily used internally by other config loaders like `loadBackendConfig` which means no changes are required for most users. + + If you use `loadConfig` directly you will need to update your usage from: + + ```diff + - const appConfigs = await loadConfig(options) + + const { appConfigs } = await loadConfig(options) + ``` + +### Patch Changes + +- 8809b6c0dd: Update the json-schema dependency version. +- Updated dependencies + - @backstage/cli-common@0.1.6 + +## 0.7.2 + +### Patch Changes + +- 0611f3b3e2: Reading app config from a remote server +- 26c5659c97: Bump msw to the same version as the rest + +## 0.7.1 + +### Patch Changes + +- 10615525f3: Switch to use the json and observable types from `@backstage/types` +- ea21f7f567: bump `typescript-json-schema` from 0.50.1 to 0.51.0 +- Updated dependencies + - @backstage/config@0.1.11 + - @backstage/cli-common@0.1.5 + - @backstage/errors@0.1.4 + +## 0.7.0 + +### Minor Changes + +- 7e97d0b8c1: Removed the `EnvFunc` public export. Its only usage was to be passed in to `LoadConfigOptions.experimentalEnvFunc`. If you were using this type, add a definition in your own project instead with the signature `(name: string) => Promise`. + +### Patch Changes + +- 223e8de6b4: Configuration schema errors are now filtered using the provided visibility option. This means that schema errors due to missing backend configuration will no longer break frontend builds. +- 7e97d0b8c1: Add public tags and documentation +- 36e67d2f24: Internal updates to apply more strict checks to throw errors. +- Updated dependencies + - @backstage/errors@0.1.3 + +## 0.6.10 + +### Patch Changes + +- 957e4b3351: Updated dependencies +- Updated dependencies + - @backstage/cli-common@0.1.4 + +## 0.6.9 + +### Patch Changes + +- ee7a1a4b64: Add option to collect configuration schemas from explicit package paths in addition to by package name. +- e68bd978e2: Allow collection of configuration schemas from multiple versions of the same package. + +## 0.6.8 + +### Patch Changes + +- d1da88a19: Properly export all used types. +- Updated dependencies + - @backstage/cli-common@0.1.3 + - @backstage/config@0.1.9 + +## 0.6.7 + +### Patch Changes + +- 0ade9d02b: Include `devDependencies` and `optionalDependencies` in the detection of Backstage packages when collecting configuration schema. +- 9b8cec063: Add support for config file watching through a new group of `watch` options to `loadConfig`. +- Updated dependencies + - @backstage/config@0.1.7 + +## 0.6.6 + +### Patch Changes + +- e9d3983ee: Add option to populate the `filteredKeys` property when processing configuration with a schema. +- Updated dependencies + - @backstage/config@0.1.6 + +## 0.6.5 + +### Patch Changes + +- ae84b20cf: Revert the upgrade to `fs-extra@10.0.0` as that seemed to have broken all installs inexplicably. + +## 0.6.4 + +### Patch Changes + +- f00493739: Removed workaround for breaking change in typescript 4.3 and bump `typescript-json-schema` instead. This should again allow the usage of `@items.visibility ` to set the visibility of array items. + +## 0.6.3 + +### Patch Changes + +- 2cf98d279: Resolve the path to app-config.yaml from the current working directory. This will allow use of `yarn link` or running the CLI in other directories and improve the experience for local backstage development. +- 438a512eb: Fixed configuration schema parsing when using TypeScript `4.3`. + +## 0.6.2 + +### Patch Changes + +- 290405276: Updated dependencies + +## 0.6.1 + +### Patch Changes + +- d8b81fd28: Bump `json-schema` dependency from `0.2.5` to `0.3.0`. +- Updated dependencies [d8b81fd28] + - @backstage/config@0.1.5 + +## 0.6.0 + +### Minor Changes + +- 82c66b8cd: Fix bug where `${...}` was not being escaped to `${...}` + + Add support for environment variable substitution in `$include`, `$file` and + `$env` transform values. + + - This change allows for including dynamic paths, such as environment specific + secrets by using the same environment variable substitution (`${..}`) already + supported outside of the various include transforms. + - If you are currently using the syntax `${...}` in your include transform values, + you will need to escape the substitution by using `${...}` instead to maintain + the same behavior. + +## 0.5.1 + +### Patch Changes + +- 062df71db: Bump `config-loader` to `ajv` 7, to enable v7 feature use elsewhere +- e9aab60c7: Each piece of the configuration schema is now validated upfront, in order to produce more informative errors. + +## 0.5.0 + +### Minor Changes + +- ef7957be4: Removed support for the deprecated `$data` placeholder. +- ef7957be4: Enable further processing of configuration files included using the `$include` placeholder. Meaning that for example for example `$env` includes will be processed as usual in included files. + +### Patch Changes + +- ef7957be4: Added support for environment variable substitutions in string configuration values using a `${VAR}` placeholder. All environment variables must be available, or the entire expression will be evaluated to `undefined`. To escape a substitution, use `${...}`, which will end up as `${...}`. + + For example: + + ```yaml + app: + baseUrl: https://${BASE_HOST} + ``` + +## 0.4.1 + +### Patch Changes + +- ad5c56fd9: Deprecate `$data` and replace it with `$include` which allows for any type of json value to be read from external files. In addition, `$include` can be used without a path, which causes the value at the root of the file to be loaded. + + Most usages of `$data` can be directly replaced with `$include`, except if the referenced value is not a string, in which case the value needs to be changed. For example: + + ```yaml + # app-config.yaml + foo: + $data: foo.yaml#myValue # replacing with $include will turn the value into a number + $data: bar.yaml#myValue # replacing with $include is safe + + # foo.yaml + myValue: 0xf00 + + # bar.yaml + myValue: bar + ``` + +## 0.4.0 + +### Minor Changes + +- 4e7091759: Fix typo of "visibility" in config schema reference + + If you have defined a config element named `visiblity`, you + will need to fix the spelling to `visibility`. For more info, + see . + +### Patch Changes + +- b4488ddb0: Added a type alias for PositionError = GeolocationPositionError + +## 0.3.0 + +### Minor Changes + +- 1722cb53c: Added support for loading and validating configuration schemas, as well as declaring config visibility through schemas. + + The new `loadConfigSchema` function exported by `@backstage/config-loader` allows for the collection and merging of configuration schemas from all nearby dependencies of the project. + + A configuration schema is declared using the `https://backstage.io/schema/config-v1` JSON Schema meta schema, which is based on draft07. The only difference to the draft07 schema is the custom `visibility` keyword, which is used to indicate whether the given config value should be visible in the frontend or not. The possible values are `frontend`, `backend`, and `secret`, where `backend` is the default. A visibility of `secret` has the same scope at runtime, but it will be treated with more care in certain contexts, and defining both `frontend` and `secret` for the same value in two different schemas will result in an error during schema merging. + + Packages that wish to contribute configuration schema should declare it in a root `"configSchema"` field in `package.json`. The field can either contain an inlined JSON schema, or a relative path to a schema file. Schema files can be in either `.json` or `.d.ts` format. + + TypeScript configuration schema files should export a single `Config` type, for example: + + ```ts + export interface Config { + app: { + /** + * Frontend root URL + * @visibility frontend + */ + baseUrl: string; + }; + } + ``` + +## 0.2.0 + +### Minor Changes + +- 8c2b76e45: **BREAKING CHANGE** + + The existing loading of additional config files like `app-config.development.yaml` using APP_ENV or NODE_ENV has been removed. + Instead, the CLI and backend process now accept one or more `--config` flags to load config files. + + Without passing any flags, `app-config.yaml` and, if it exists, `app-config.local.yaml` will be loaded. + If passing any `--config ` flags, only those files will be loaded, **NOT** the default `app-config.yaml` one. + + The old behaviour of for example `APP_ENV=development` can be replicated using the following flags: + + ```bash + --config ../../app-config.yaml --config ../../app-config.development.yaml + ``` + +- ce5512bc0: Added support for new shorthand when defining secrets, where `$env: ENV` can be used instead of `$secret: { env: ENV }` etc. + +## @backstage/backend-defaults@0.13.0-next.2 + +### Minor Changes + +- 8495b18: Add a new `externalTokenHandlersServiceRef` to allow custom external token validations + +## @backstage/plugin-mui-to-bui@0.2.0-next.0 + +### Minor Changes + +- d5cbdba: This is the first release of the Material UI to Backstage UI migration helper plugin. It adds a new page at `/mui-to-bui` that converts an existing MUI v5 theme into Backstage UI (BUI) CSS variables, with live preview and copy/download. + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.7.2-next.1 + - @backstage/theme@0.6.9-next.0 + +## @backstage/plugin-notifications-backend-module-slack@0.2.0-next.1 + +### Minor Changes + +- 3d09bb2: Adds username as optional config in order to send Slack notifications with a specific username in the case when using one Slack App for more than just Backstage. + +## @backstage/app-defaults@1.7.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.2 + - @backstage/theme@0.6.9-next.0 + +## @backstage/cli@0.34.4-next.2 + +### Patch Changes + +- ab96bb7: Added a new `--entrypoint` option to the `package start` command, which allows you to specify a custom entry directory/file for development applications. This is particularly useful when maintaining separate dev apps for different versions of your plugin (e.g., stable and alpha). + + **Example usage:** + + Consider the following plugin dev folder structure: + + dev/ + index.tsx + alpha/ + index.ts + + - The default `yarn package start` command uses the `dev/` folder as the entry point and executes `dev/index.tsx` file; + - Running `yarn package start --entrypoint dev/alpha` will instead use `dev/alpha/` as the entry point and execute `dev/alpha/index.ts` file. + +## @backstage/core-components@0.18.2-next.2 + +### Patch Changes + +- 95935fb: Fixed dependency graph automatically scrolling forever +- Updated dependencies + - @backstage/theme@0.6.9-next.0 + +## @backstage/create-app@0.7.5-next.2 + +### Patch Changes + +- Bumped create-app version. + +## @backstage/dev-utils@1.1.15-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.2 + - @backstage/theme@0.6.9-next.0 + - @backstage/app-defaults@1.7.1-next.2 + +## @backstage/test-utils@1.7.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.9-next.0 + +## @backstage/theme@0.6.9-next.0 + +### Patch Changes + +- d5cbdba: The `UnifiedThemeProvider` now coordinates theme attributes on the document `body` in case multiple theme providers are rendered. + +## @backstage/ui@0.7.2-next.1 + +### Patch Changes + +- a9b88be: Enable tooltips on disabled buttons with automatic wrapper +- 4adbb03: Avoid overriding onChange when spreading props + +## @backstage/plugin-app@0.3.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.2 + - @backstage/theme@0.6.9-next.0 + +## @backstage/plugin-auth@0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.2 + - @backstage/theme@0.6.9-next.0 + +## @backstage/plugin-catalog-backend@3.1.2-next.2 + +### Patch Changes + +- 6493c98: Log before provider-orphaning eviction happens +- 77516c5: Added new `catalog:validate-entity` action to actions registry. + + This action can be used to validate entities against the software catalog. + This is useful for validating `catalog-info.yaml` file changes locally using the + Backstage MCP server. + +## @backstage/plugin-home@0.8.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.2 + - @backstage/theme@0.6.9-next.0 + +## @backstage/plugin-kubernetes-react@0.5.12-next.2 + +### Patch Changes + +- ac405f2: The configmaps added to be rendered +- f7a4144: Fixes calculation of CPU utilization in the PodTable +- Updated dependencies + - @backstage/core-components@0.18.2-next.2 + +## @backstage/plugin-notifications@0.5.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.2 + - @backstage/theme@0.6.9-next.0 + +## @backstage/plugin-notifications-backend@0.5.11-next.1 + +### Patch Changes + +- 3b8e156: Fixed exclude entity reference not working in notification sending + +## @backstage/plugin-org@0.6.45-next.2 + +### Patch Changes + +- 8b7351f: Add `initialRelationAggregation` and `showAggregateMembersToggle` options to `EntityMembersListCard` as well to `EntityOwnershipCard` +- Updated dependencies + - @backstage/core-components@0.18.2-next.2 + +## @backstage/plugin-scaffolder@1.34.2-next.2 + +### Patch Changes + +- 075e064: Added missing form fields for the new frontend system. +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.19.2-next.2 + - @backstage/core-components@0.18.2-next.2 + - @backstage/plugin-techdocs-react@1.3.4-next.1 + +## @backstage/plugin-scaffolder-react@1.19.2-next.2 + +### Patch Changes + +- e61f89e: Don't change loading to false until we've actually got some log state +- Updated dependencies + - @backstage/core-components@0.18.2-next.2 + - @backstage/theme@0.6.9-next.0 + +## @backstage/plugin-search-react@1.9.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.2 + - @backstage/theme@0.6.9-next.0 + +## @backstage/plugin-signals@0.0.24-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.2 + - @backstage/theme@0.6.9-next.0 + +## @backstage/plugin-techdocs@1.15.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.2 + - @backstage/theme@0.6.9-next.0 + - @backstage/plugin-search-react@1.9.5-next.2 + - @backstage/plugin-techdocs-react@1.3.4-next.1 + +## @backstage/plugin-user-settings@0.8.27-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.2 + - @backstage/theme@0.6.9-next.0 + +## example-app@0.2.114-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-mui-to-bui@0.2.0-next.0 + - @backstage/plugin-scaffolder-react@1.19.2-next.2 + - @backstage/plugin-org@0.6.45-next.2 + - @backstage/cli@0.34.4-next.2 + - @backstage/ui@0.7.2-next.1 + - @backstage/core-components@0.18.2-next.2 + - @backstage/theme@0.6.9-next.0 + - @backstage/plugin-scaffolder@1.34.2-next.2 + - @backstage/app-defaults@1.7.1-next.2 + - @backstage/plugin-home@0.8.13-next.2 + - @backstage/plugin-notifications@0.5.10-next.2 + - @backstage/plugin-search-react@1.9.5-next.2 + - @backstage/plugin-signals@0.0.24-next.2 + - @backstage/plugin-techdocs@1.15.1-next.2 + - @backstage/plugin-techdocs-react@1.3.4-next.1 + - @backstage/plugin-user-settings@0.8.27-next.2 + +## example-app-next@0.0.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.19.2-next.2 + - @backstage/plugin-org@0.6.45-next.2 + - @backstage/cli@0.34.4-next.2 + - @backstage/ui@0.7.2-next.1 + - @backstage/core-components@0.18.2-next.2 + - @backstage/theme@0.6.9-next.0 + - @backstage/plugin-scaffolder@1.34.2-next.2 + - @backstage/app-defaults@1.7.1-next.2 + - @backstage/plugin-app@0.3.1-next.2 + - @backstage/plugin-auth@0.1.1-next.1 + - @backstage/plugin-home@0.8.13-next.2 + - @backstage/plugin-notifications@0.5.10-next.2 + - @backstage/plugin-search-react@1.9.5-next.2 + - @backstage/plugin-signals@0.0.24-next.2 + - @backstage/plugin-techdocs@1.15.1-next.2 + - @backstage/plugin-techdocs-react@1.3.4-next.1 + - @backstage/plugin-user-settings@0.8.27-next.2 + +## techdocs-cli-embedded-app@0.2.113-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.34.4-next.2 + - @backstage/core-components@0.18.2-next.2 + - @backstage/theme@0.6.9-next.0 + - @backstage/app-defaults@1.7.1-next.2 + - @backstage/test-utils@1.7.12-next.1 + - @backstage/plugin-techdocs@1.15.1-next.2 + - @backstage/plugin-techdocs-react@1.3.4-next.1 diff --git a/docs/tooling/cli/03-commands.md b/docs/tooling/cli/03-commands.md index d176278655..ce8f862c98 100644 --- a/docs/tooling/cli/03-commands.md +++ b/docs/tooling/cli/03-commands.md @@ -177,11 +177,12 @@ Usage: backstage-cli package start [options] Start a package for local development Options: - --config Config files to load instead of app-config.yaml (default: []) - --role Run the command with an explicit package role - --check Enable type checking and linting if available - --inspect Enable debugger in Node.js environments - --inspect-brk Enable debugger in Node.js environments, breaking before code starts + --config Config files to load instead of app-config.yaml (default: []) + --role Run the command with an explicit package role + --check Enable type checking and linting if available + --inspect Enable debugger in Node.js environments + --inspect-brk Enable debugger in Node.js environments, breaking before code starts + --entrypoint Entry directory path (uses index file) or entry file path (without extension). Defaults to "dev" ``` ## package build diff --git a/docs/tooling/cli/04-templates.md b/docs/tooling/cli/04-templates.md index c3bfb57d11..f634894c4f 100644 --- a/docs/tooling/cli/04-templates.md +++ b/docs/tooling/cli/04-templates.md @@ -147,3 +147,13 @@ The `role` property in the template yaml file is used to determine what input wi | `plugin-web-library` | `pluginId` | `plugins` | none | | `plugin-node-library` | `pluginId` | `plugins` | none | | `plugin-common-library` | `pluginId` | `plugins` | none | + +## Dependency Versioning + +The `yarn new` command automatically detects if the [Backstage Yarn plugin](https://github.com/backstage/backstage/tree/master/packages/yarn-plugin) is installed in your repository and adjusts dependency versioning accordingly. + +When the Backstage Yarn plugin is installed (detected via `.yarnrc.yml`), `yarn new` will generate `backstage:^` ranges for all `@backstage/*` dependencies. This ensures that new packages use the same Backstage version as defined in your `backstage.json` file. + +When the plugin is not installed, `yarn new` uses the standard npm version ranges (e.g., `^1.0.0`) for all dependencies, maintaining backward compatibility. + +Regardless of plugin installation, `workspace:` ranges found in your `yarn.lock` file will always take precedence over both `backstage:^` and npm ranges. This ensures that packages within monorepos continue to use workspace linking when available. diff --git a/docs/tooling/package-metadata.md b/docs/tooling/package-metadata.md index ad9d5ec5a0..3911ada413 100644 --- a/docs/tooling/package-metadata.md +++ b/docs/tooling/package-metadata.md @@ -96,6 +96,32 @@ This field can be generated by the `backstage-cli repo fix --publish` command. T The presence of this field is checked by the `backstage-cli package prepack` command, which is used to prepare a package for publishing. You can read more about this requirement in the section on [metadata for published packages](#metadata-for-published-packages). +### `backstage.features` + +This field declares where to find Backstage features exported from the package. It is a map of features export paths to their feature type, similar to the `exports` field. + +```js title="Example usage of the backstage.features field" +{ + "name": "@backstage/plugin-catalog", + "backstage": { + "features": { + "./alpha": "@backstage/FrontendPlugin" + } + } +} +``` + +This field is automatically generated by the `backstage-cli package prepack` command when publishing a package and therefore does not need to be committed to the repository. + +If you are using your own tooling to publish a package, you need to populate this field manually, using the following feature types: + +- `@backstage/BackendFeature` +- `@backstage/FrontendPlugin` +- `@backstage/FrontendModule` +- `@backstage/FrontendFeatureLoader` + +You can read more about the feature types in the [backend system](../backend-system/architecture/01-index.md) and [frontend system](../frontend-system/architecture/00-index.md) documentation. + ### `backstage.pluginPackages` For any package that is part of a plugin, this field should be set to a list of all packages that are directly part of the same plugin. This includes frontend and backend plugin packages as well as related libraries, but not modules. diff --git a/docs/tutorials/manual-knex-rollback.md b/docs/tutorials/manual-knex-rollback.md index b0f67ba3e0..442a48e2f5 100644 --- a/docs/tutorials/manual-knex-rollback.md +++ b/docs/tutorials/manual-knex-rollback.md @@ -23,7 +23,7 @@ You can interact with Knex running the commands below in the project root: We want to check the migration status: ```sh -$ node_modules/.bin/knex migrate:status --connection "postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@$POSTGRES_HOST/backstage_plugin_app" --client pg --migrations-directory node_modules/@backstage/plugin-catalog-backend/migrations/ +$ node_modules/.bin/knex migrate:status --connection "postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@$POSTGRES_HOST/backstage_plugin_catalog" --client pg --migrations-directory node_modules/@backstage/plugin-catalog-backend/migrations/ Using environment: production Found 2 Completed Migration file/files. 20211229105307_init.js @@ -34,7 +34,7 @@ No Pending Migration files Found. Now lets rollback a specific migration called `20240113144027_assets-namespace.js`: ```sh -$ node_modules/.bin/knex migrate:down 20240113144027_assets-namespace.js --connection "postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@$POSTGRES_HOST/backstage_plugin_app" --client pg --migrations-directory node_modules/@backstage/plugin-catalog-backend/migrations/ +$ node_modules/.bin/knex migrate:down 20240113144027_assets-namespace.js --connection "postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@$POSTGRES_HOST/backstage_plugin_catalog" --client pg --migrations-directory node_modules/@backstage/plugin-catalog-backend/migrations/ Using environment: production Batch 2 rolled back the following migrations: 20240113144027_assets-namespace.js @@ -43,7 +43,7 @@ Batch 2 rolled back the following migrations: Now we can check the migration status again to confirm the rollback: ```sh -$ node_modules/.bin/knex migrate:status --connection "postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@$POSTGRES_HOST/backstage_plugin_app" --client pg --migrations-directory node_modules/@backstage/plugin-catalog-backend/migrations/ +$ node_modules/.bin/knex migrate:status --connection "postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@$POSTGRES_HOST/backstage_plugin_catalog" --client pg --migrations-directory node_modules/@backstage/plugin-catalog-backend/migrations/ Using environment: production Found 1 Completed Migration file/files. 20211229105307_init.js @@ -54,7 +54,7 @@ Found 1 Pending Migration file/files. Now lets use `migrate:currentVersion` which retrieves the current migration version. If there aren't any migrations run yet, it will return "none". ```sh -$ node_modules/.bin/knex migrate:currentVersion --connection "postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@$POSTGRES_HOST/backstage_plugin_app" --client pg +$ node_modules/.bin/knex migrate:currentVersion --connection "postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@$POSTGRES_HOST/backstage_plugin_catalog" --client pg Using environment: production Current Version: 20240113144027 ``` diff --git a/microsite/data/plugins/kwirthlog.yaml b/microsite/data/plugins/kwirthlog.yaml new file mode 100644 index 0000000000..49863c2d4e --- /dev/null +++ b/microsite/data/plugins/kwirthlog.yaml @@ -0,0 +1,10 @@ +--- +title: KwirthLog for Backstage +author: Julio Fernandez +authorUrl: https://github.com/jfvilas +category: Monitoring +description: With KwirthLog you can view real-time log streams of Kubernetes objects linked to Backstage entities (source entities can be grouped and merged) +documentation: https://github.com/jfvilas/plugin-kwirth-log +iconUrl: https://raw.githubusercontent.com/jfvilas/plugin-kwirth-log/master/src/assets/kwirthlog-logo.png +npmPackageName: '@jfvilas/plugin-kwirth-log' +addedDate: '2025-09-18' diff --git a/microsite/sidebars.ts b/microsite/sidebars.ts index 72608f4bac..bdebf915df 100644 --- a/microsite/sidebars.ts +++ b/microsite/sidebars.ts @@ -22,9 +22,8 @@ export default { docs: { Overview: [ 'overview/what-is-backstage', + 'overview/technical-overview', 'overview/architecture-overview', - 'overview/background', - 'overview/vision', 'overview/roadmap', 'overview/threat-model', 'overview/versioning-policy', diff --git a/package.json b/package.json index 69e30f5cc9..a22ab1666a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "1.43.0", + "version": "1.44.0-next.2", "backstage": { "cli": { "new": { @@ -102,7 +102,6 @@ "@changesets/assemble-release-plan@^6.0.0": "patch:@changesets/assemble-release-plan@npm%3A6.0.0#./.yarn/patches/@changesets-assemble-release-plan-npm-6.0.0-f7b3005037.patch", "@material-ui/pickers@^3.2.10": "patch:@material-ui/pickers@npm%3A3.3.11#./.yarn/patches/@material-ui-pickers-npm-3.3.11-1c8f68ea20.patch", "@material-ui/pickers@^3.3.10": "patch:@material-ui/pickers@npm%3A3.3.11#./.yarn/patches/@material-ui-pickers-npm-3.3.11-1c8f68ea20.patch", - "@storybook/react@npm:9.1.5": "patch:@storybook/react@npm%3A9.1.5#~/.yarn/patches/@storybook-react-npm-9.1.5-2331f18b6b.patch", "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", "@yarnpkg/plugin-npm@npm:^3.1.0": "patch:@yarnpkg/plugin-npm@npm%3A3.1.0#~/.yarn/patches/@yarnpkg-plugin-npm-npm-3.1.0-6533d0f5a1.patch", @@ -115,6 +114,7 @@ "csstype@npm:^3.1.2": "3.0.9", "csstype@npm:^3.1.3": "3.0.9", "jest-haste-map@^29.7.0": "patch:jest-haste-map@npm%3A29.7.0#./.yarn/patches/jest-haste-map-npm-29.7.0-e3be419eff.patch", + "GendocuPublicApis": "npm:gendocu-public-apis@^1.0.0", "recast@npm:0.23.9>ast-types": "patch:ast-types@npm%3A0.16.1#./.yarn/patches/ast-types-npm-0.16.1-43c4ac4b0d.patch" }, "dependencies": { @@ -135,11 +135,11 @@ "@octokit/rest": "^19.0.3", "@playwright/test": "^1.32.3", "@spotify/eslint-plugin": "^15.0.0", - "@storybook/addon-a11y": "^9.1.5", - "@storybook/addon-docs": "^9.1.5", - "@storybook/addon-links": "^9.1.5", - "@storybook/addon-themes": "^9.1.5", - "@storybook/react-vite": "^9.1.5", + "@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", "@techdocs/cli": "workspace:*", "@types/cacheable-request": "^8.3.6", "@types/memjs": "^1.3.3", @@ -164,7 +164,7 @@ "shx": "^0.4.0", "sloc": "^0.3.1", "sort-package-json": "^2.8.0", - "storybook": "^9.1.5", + "storybook": "^9.1.7", "typedoc": "^0.28.0", "typescript": "~5.7.0", "vite": "^7.1.5" diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md index 02f126bcf5..2253e0e2cd 100644 --- a/packages/app-defaults/CHANGELOG.md +++ b/packages/app-defaults/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/app-defaults +## 1.7.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.2 + - @backstage/theme@0.6.9-next.0 + +## 1.7.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.1-next.0 + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/plugin-permission-react@0.4.37-next.0 + +## 1.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/core-app-api@1.19.0 + - @backstage/core-plugin-api@1.11.0 + - @backstage/theme@0.6.8 + - @backstage/plugin-permission-react@0.4.36 + ## 1.7.0 ### Minor Changes diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json index 37973fbf0f..3361b7e7b3 100644 --- a/packages/app-defaults/package.json +++ b/packages/app-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/app-defaults", - "version": "1.7.0", + "version": "1.7.1-next.2", "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 7949e120fa..4dfaaa596d 100644 --- a/packages/app-next-example-plugin/CHANGELOG.md +++ b/packages/app-next-example-plugin/CHANGELOG.md @@ -1,5 +1,13 @@ # app-next-example-plugin +## 0.0.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + ## 0.0.27 ### Patch Changes diff --git a/packages/app-next-example-plugin/package.json b/packages/app-next-example-plugin/package.json index 3b76202029..384911f923 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.27", + "version": "0.0.28-next.0", "description": "Backstage internal example plugin", "backstage": { "role": "frontend-plugin", diff --git a/packages/app-next/CHANGELOG.md b/packages/app-next/CHANGELOG.md index 7e66d0d041..95149a2a8a 100644 --- a/packages/app-next/CHANGELOG.md +++ b/packages/app-next/CHANGELOG.md @@ -1,5 +1,119 @@ # example-app-next +## 0.0.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.19.2-next.2 + - @backstage/plugin-org@0.6.45-next.2 + - @backstage/cli@0.34.4-next.2 + - @backstage/ui@0.7.2-next.1 + - @backstage/core-components@0.18.2-next.2 + - @backstage/theme@0.6.9-next.0 + - @backstage/plugin-scaffolder@1.34.2-next.2 + - @backstage/app-defaults@1.7.1-next.2 + - @backstage/plugin-app@0.3.1-next.2 + - @backstage/plugin-auth@0.1.1-next.1 + - @backstage/plugin-home@0.8.13-next.2 + - @backstage/plugin-notifications@0.5.10-next.2 + - @backstage/plugin-search-react@1.9.5-next.2 + - @backstage/plugin-signals@0.0.24-next.2 + - @backstage/plugin-techdocs@1.15.1-next.2 + - @backstage/plugin-techdocs-react@1.3.4-next.1 + - @backstage/plugin-user-settings@0.8.27-next.2 + +## 0.0.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/cli@0.34.4-next.1 + - @backstage/core-app-api@1.19.1-next.0 + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/frontend-app-api@0.13.1-next.1 + - @backstage/frontend-defaults@0.3.2-next.1 + - @backstage/integration-react@1.2.11-next.1 + - @backstage/plugin-catalog-import@0.13.6-next.1 + - @backstage/plugin-home@0.8.13-next.1 + - @backstage/plugin-permission-react@0.4.37-next.0 + - @backstage/plugin-techdocs@1.15.1-next.1 + - @backstage/plugin-techdocs-react@1.3.4-next.1 + - @backstage/app-defaults@1.7.1-next.1 + - @backstage/core-compat-api@0.5.3-next.1 + - @backstage/plugin-api-docs@0.13.0-next.1 + - @backstage/plugin-catalog@1.31.4-next.1 + - @backstage/plugin-catalog-graph@0.5.2-next.1 + - @backstage/plugin-catalog-react@1.21.2-next.1 + - @backstage/plugin-notifications@0.5.10-next.1 + - @backstage/plugin-org@0.6.45-next.1 + - @backstage/plugin-scaffolder@1.34.2-next.1 + - @backstage/plugin-scaffolder-react@1.19.2-next.1 + - @backstage/plugin-search@1.4.31-next.1 + - @backstage/plugin-search-react@1.9.5-next.1 + - @backstage/plugin-signals@0.0.24-next.1 + - @backstage/plugin-user-settings@0.8.27-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/plugin-app@0.3.1-next.1 + - @backstage/plugin-app-visualizer@0.1.24-next.1 + - @backstage/plugin-auth-react@0.1.20-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.22-next.1 + - @backstage/plugin-kubernetes@0.12.12-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.30-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.29-next.1 + - @backstage/plugin-auth@0.1.1-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + - @backstage/plugin-search-common@1.2.20-next.0 + +## 0.0.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.7.2-next.0 + - @backstage/plugin-catalog-graph@0.5.2-next.0 + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/core-components@0.18.2-next.0 + - @backstage/cli@0.34.4-next.0 + - @backstage/plugin-scaffolder@1.34.2-next.0 + - @backstage/plugin-api-docs@0.13.0-next.0 + - @backstage/core-compat-api@0.5.3-next.0 + - @backstage/plugin-catalog@1.31.4-next.0 + - @backstage/plugin-catalog-import@0.13.6-next.0 + - @backstage/plugin-home@0.8.13-next.0 + - @backstage/plugin-kubernetes@0.12.12-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.30-next.0 + - @backstage/plugin-org@0.6.45-next.0 + - @backstage/plugin-scaffolder-react@1.19.2-next.0 + - @backstage/plugin-search@1.4.31-next.0 + - @backstage/plugin-techdocs@1.15.1-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.29-next.0 + - @backstage/plugin-user-settings@0.8.27-next.0 + - @backstage/app-defaults@1.7.1-next.0 + - @backstage/frontend-defaults@0.3.2-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/integration-react@1.2.11-next.0 + - @backstage/plugin-app@0.3.1-next.0 + - @backstage/plugin-app-visualizer@0.1.24-next.0 + - @backstage/plugin-auth@0.1.1-next.0 + - @backstage/plugin-auth-react@0.1.20-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.22-next.0 + - @backstage/plugin-notifications@0.5.10-next.0 + - @backstage/plugin-search-react@1.9.5-next.0 + - @backstage/plugin-signals@0.0.24-next.0 + - @backstage/plugin-techdocs-react@1.3.4-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.19.0 + - @backstage/core-plugin-api@1.11.0 + - @backstage/frontend-app-api@0.13.1-next.0 + - @backstage/theme@0.6.8 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-search-common@1.2.19 + ## 0.0.27 ### Patch Changes diff --git a/packages/app-next/app-config.yaml b/packages/app-next/app-config.yaml index 35380c961f..dcaddfb9bf 100644 --- a/packages/app-next/app-config.yaml +++ b/packages/app-next/app-config.yaml @@ -21,6 +21,11 @@ app: ownerEntityRefs: [cubic-belugas] extensions: + - entity-card:org/members-list: + config: + showAggregateMembersToggle: true + initialRelationAggregation: aggregated + # - apis.plugin.graphiql.browse.gitlab: true # - graphiql-endpoint:graphiql/gitlab: true diff --git a/packages/app-next/package.json b/packages/app-next/package.json index f6f9f52e8d..c64f548c78 100644 --- a/packages/app-next/package.json +++ b/packages/app-next/package.json @@ -1,6 +1,6 @@ { "name": "example-app-next", - "version": "0.0.27", + "version": "0.0.28-next.2", "backstage": { "role": "frontend" }, diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index 533f4ab84c..c7f8d48b37 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,108 @@ # example-app +## 0.2.114-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-mui-to-bui@0.2.0-next.0 + - @backstage/plugin-scaffolder-react@1.19.2-next.2 + - @backstage/plugin-org@0.6.45-next.2 + - @backstage/cli@0.34.4-next.2 + - @backstage/ui@0.7.2-next.1 + - @backstage/core-components@0.18.2-next.2 + - @backstage/theme@0.6.9-next.0 + - @backstage/plugin-scaffolder@1.34.2-next.2 + - @backstage/app-defaults@1.7.1-next.2 + - @backstage/plugin-home@0.8.13-next.2 + - @backstage/plugin-notifications@0.5.10-next.2 + - @backstage/plugin-search-react@1.9.5-next.2 + - @backstage/plugin-signals@0.0.24-next.2 + - @backstage/plugin-techdocs@1.15.1-next.2 + - @backstage/plugin-techdocs-react@1.3.4-next.1 + - @backstage/plugin-user-settings@0.8.27-next.2 + +## 0.2.114-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/cli@0.34.4-next.1 + - @backstage/core-app-api@1.19.1-next.0 + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/frontend-app-api@0.13.1-next.1 + - @backstage/integration-react@1.2.11-next.1 + - @backstage/plugin-catalog-import@0.13.6-next.1 + - @backstage/plugin-home@0.8.13-next.1 + - @backstage/plugin-permission-react@0.4.37-next.0 + - @backstage/plugin-techdocs@1.15.1-next.1 + - @backstage/plugin-techdocs-react@1.3.4-next.1 + - @backstage/app-defaults@1.7.1-next.1 + - @backstage/plugin-api-docs@0.13.0-next.1 + - @backstage/plugin-catalog@1.31.4-next.1 + - @backstage/plugin-catalog-graph@0.5.2-next.1 + - @backstage/plugin-catalog-react@1.21.2-next.1 + - @backstage/plugin-notifications@0.5.10-next.1 + - @backstage/plugin-org@0.6.45-next.1 + - @backstage/plugin-scaffolder@1.34.2-next.1 + - @backstage/plugin-scaffolder-react@1.19.2-next.1 + - @backstage/plugin-search@1.4.31-next.1 + - @backstage/plugin-search-react@1.9.5-next.1 + - @backstage/plugin-signals@0.0.24-next.1 + - @backstage/plugin-user-settings@0.8.27-next.1 + - @backstage/plugin-auth-react@0.1.20-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.22-next.1 + - @backstage/plugin-devtools@0.1.32-next.1 + - @backstage/plugin-kubernetes@0.12.12-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.30-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.29-next.1 + - @backstage/plugin-catalog-common@1.1.6-next.0 + - @backstage/plugin-search-common@1.2.20-next.0 + +## 0.2.114-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.7.2-next.0 + - @backstage/plugin-catalog-graph@0.5.2-next.0 + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/core-components@0.18.2-next.0 + - @backstage/cli@0.34.4-next.0 + - @backstage/plugin-scaffolder@1.34.2-next.0 + - @backstage/plugin-api-docs@0.13.0-next.0 + - @backstage/plugin-catalog@1.31.4-next.0 + - @backstage/plugin-catalog-import@0.13.6-next.0 + - @backstage/plugin-home@0.8.13-next.0 + - @backstage/plugin-kubernetes@0.12.12-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.30-next.0 + - @backstage/plugin-org@0.6.45-next.0 + - @backstage/plugin-scaffolder-react@1.19.2-next.0 + - @backstage/plugin-search@1.4.31-next.0 + - @backstage/plugin-techdocs@1.15.1-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.29-next.0 + - @backstage/plugin-user-settings@0.8.27-next.0 + - @backstage/app-defaults@1.7.1-next.0 + - @backstage/integration-react@1.2.11-next.0 + - @backstage/plugin-auth-react@0.1.20-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.22-next.0 + - @backstage/plugin-devtools@0.1.32-next.0 + - @backstage/plugin-notifications@0.5.10-next.0 + - @backstage/plugin-search-react@1.9.5-next.0 + - @backstage/plugin-signals@0.0.24-next.0 + - @backstage/plugin-techdocs-react@1.3.4-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.19.0 + - @backstage/core-plugin-api@1.11.0 + - @backstage/frontend-app-api@0.13.1-next.0 + - @backstage/theme@0.6.8 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-search-common@1.2.19 + ## 0.2.113 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index 90ffcd0b3b..b1cd05ac7a 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "example-app", - "version": "0.2.113", + "version": "0.2.114-next.2", "backstage": { "role": "frontend" }, @@ -56,6 +56,7 @@ "@backstage/plugin-home": "workspace:^", "@backstage/plugin-kubernetes": "workspace:^", "@backstage/plugin-kubernetes-cluster": "workspace:^", + "@backstage/plugin-mui-to-bui": "workspace:^", "@backstage/plugin-notifications": "workspace:^", "@backstage/plugin-org": "workspace:^", "@backstage/plugin-permission-react": "workspace:^", diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index ad525420a7..45f91ce8a9 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -73,6 +73,7 @@ import { } from '@backstage/plugin-notifications'; import { CustomizableHomePage } from './components/home/CustomizableHomePage'; import { HomePage } from './components/home/HomePage'; +import { BuiThemerPage } from '@backstage/plugin-mui-to-bui'; const app = createApp({ apis, @@ -208,6 +209,7 @@ const routes = ( {customDevToolsPage} } /> + } /> ); diff --git a/packages/backend-app-api/CHANGELOG.md b/packages/backend-app-api/CHANGELOG.md index d965783fac..a0176a008f 100644 --- a/packages/backend-app-api/CHANGELOG.md +++ b/packages/backend-app-api/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/backend-app-api +## 1.2.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + ## 1.2.7 ### Patch Changes diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index 9407f28707..b082de0cfa 100644 --- a/packages/backend-app-api/package.json +++ b/packages/backend-app-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-app-api", - "version": "1.2.7", + "version": "1.2.8-next.0", "description": "Core API used by Backstage backend apps", "backstage": { "role": "node-library" diff --git a/packages/backend-defaults/CHANGELOG.md b/packages/backend-defaults/CHANGELOG.md index 1adfd0f80e..5cf7aa3414 100644 --- a/packages/backend-defaults/CHANGELOG.md +++ b/packages/backend-defaults/CHANGELOG.md @@ -1,5 +1,57 @@ # @backstage/backend-defaults +## 0.13.0-next.2 + +### Minor Changes + +- 8495b18: Add a new `externalTokenHandlersServiceRef` to allow custom external token validations + +## 0.13.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.4-next.0 + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/integration-aws-node@0.1.18-next.0 + - @backstage/backend-app-api@1.2.8-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + - @backstage/plugin-permission-node@0.10.5-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + - @backstage/cli-node@0.2.14 + +## 0.13.0-next.0 + +### Minor Changes + +- 2d3e2b2: implement support for direct url for AzureBlobStorageUrlReader search function +- 8b91238: Adds support for configuring server-level HTTP options through the + `app-config.yaml` file under the `backend.server` key. Supported options + include `headersTimeout`, `keepAliveTimeout`, `requestTimeout`, `timeout`, + `maxHeadersCount`, and `maxRequestsPerSocket`. + + These are passed directly to the underlying Node.js HTTP server. + If omitted, Node.js defaults are used. + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/backend-app-api@1.2.7 + - @backstage/plugin-auth-node@0.6.7 + - @backstage/plugin-permission-node@0.10.4 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/cli-node@0.2.14 + - @backstage/config@1.3.3 + - @backstage/config-loader@1.10.3 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.17 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.15 + ## 0.12.1 ### Patch Changes diff --git a/packages/backend-defaults/config.d.ts b/packages/backend-defaults/config.d.ts index 2982557f32..f0632a37bb 100644 --- a/packages/backend-defaults/config.d.ts +++ b/packages/backend-defaults/config.d.ts @@ -95,6 +95,31 @@ export interface Config { }; }; + /** + * Server-level HTTP options configuration for the backend. + * These options are passed directly to the underlying Node.js HTTP server. + * + * Timeout values support multiple formats: + * - A number in milliseconds + * - A string in the format of '1d', '2 seconds' etc. as supported by the `ms` library + * - A standard ISO formatted duration string, e.g. 'P2DT6H' or 'PT1M' + * - An object with individual units (in plural) as keys, e.g. `{ days: 2, hours: 6 }` + */ + server?: { + /** Sets the timeout value for receiving the complete HTTP headers from the client. */ + headersTimeout?: number | string | HumanDuration; + /** Sets the timeout value for receiving the entire request (headers and body) from the client. */ + requestTimeout?: number | string | HumanDuration; + /** Sets the timeout value for inactivity on a socket during keep-alive. */ + keepAliveTimeout?: number | string | HumanDuration; + /** Sets the timeout value for sockets. */ + timeout?: number | string | HumanDuration; + /** Limits maximum incoming headers count. */ + maxHeadersCount?: number; + /** Sets the maximum number of requests socket can handle before closing keep alive connection. */ + maxRequestsPerSocket?: number; + }; + /** * Options used by the default auditor service. */ diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index 013e32db59..b5a4acd229 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-defaults", - "version": "0.12.1", + "version": "0.13.0-next.2", "description": "Backend defaults used by Backstage backend apps", "backstage": { "role": "node-library" diff --git a/packages/backend-defaults/report-auth.api.md b/packages/backend-defaults/report-auth.api.md index 1883e65a6a..2fc0f16452 100644 --- a/packages/backend-defaults/report-auth.api.md +++ b/packages/backend-defaults/report-auth.api.md @@ -4,6 +4,7 @@ ```ts import { AuthService } from '@backstage/backend-plugin-api'; +import type { Config } from '@backstage/config'; import { ServiceFactory } from '@backstage/backend-plugin-api'; import { ServiceRef } from '@backstage/backend-plugin-api'; @@ -14,6 +15,36 @@ export const authServiceFactory: ServiceFactory< 'singleton' >; +// @public +export function createExternalTokenHandler( + handler: ExternalTokenHandler, +): ExternalTokenHandler; + +// @public +export interface ExternalTokenHandler { + // (undocumented) + initialize(ctx: { options: Config }): TContext; + // (undocumented) + type: string; + // (undocumented) + verifyToken( + token: string, + ctx: TContext, + ): Promise< + | { + subject: string; + } + | undefined + >; +} + +// @public +export const externalTokenHandlersServiceRef: ServiceRef< + ExternalTokenHandler, + 'plugin', + 'multiton' +>; + // @public export interface PluginTokenHandler { // (undocumented) diff --git a/packages/backend-defaults/report-urlReader.api.md b/packages/backend-defaults/report-urlReader.api.md index 03a112abc7..039d51808d 100644 --- a/packages/backend-defaults/report-urlReader.api.md +++ b/packages/backend-defaults/report-urlReader.api.md @@ -87,7 +87,10 @@ export class AzureBlobStorageUrlReader implements UrlReaderService { options?: UrlReaderServiceReadUrlOptions, ): Promise; // (undocumented) - search(): Promise; + search( + url: string, + options?: UrlReaderServiceSearchOptions, + ): Promise; // (undocumented) toString(): string; } diff --git a/packages/backend-defaults/src/entrypoints/auth/DefaultAuthService.ts b/packages/backend-defaults/src/entrypoints/auth/DefaultAuthService.ts index d8cb60e8b5..dba4f70dec 100644 --- a/packages/backend-defaults/src/entrypoints/auth/DefaultAuthService.ts +++ b/packages/backend-defaults/src/entrypoints/auth/DefaultAuthService.ts @@ -25,7 +25,7 @@ import { import { AuthenticationError } from '@backstage/errors'; import { JsonObject } from '@backstage/types'; import { decodeJwt } from 'jose'; -import { ExternalTokenHandler } from './external/ExternalTokenHandler'; +import { ExternalAuthTokenHandler } from './external/ExternalAuthTokenHandler'; import { createCredentialsWithNonePrincipal, createCredentialsWithServicePrincipal, @@ -41,7 +41,7 @@ export class DefaultAuthService implements AuthService { constructor( private readonly userTokenHandler: UserTokenHandler, private readonly pluginTokenHandler: PluginTokenHandler, - private readonly externalTokenHandler: ExternalTokenHandler, + private readonly externalTokenHandler: ExternalAuthTokenHandler, private readonly pluginId: string, private readonly disableDefaultAuthPolicy: boolean, private readonly pluginKeySource: PluginKeySource, diff --git a/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.test.ts b/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.test.ts index a244614135..b70aedfb9a 100644 --- a/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.test.ts +++ b/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.test.ts @@ -31,6 +31,9 @@ import { toInternalBackstageCredentials } from './helpers'; import { PluginTokenHandler } from './plugin/PluginTokenHandler'; import { createServiceFactory } from '@backstage/backend-plugin-api'; +import { externalTokenHandlersServiceRef } from './external/ExternalAuthTokenHandler'; +import { createExternalTokenHandler } from './external/helpers'; + const server = setupServer(); // TODO: Ship discovery mock service in the service factory tester @@ -450,8 +453,88 @@ describe('authServiceFactory', () => { dependencies: [...mockDeps, customPluginTokenHandler], }); const searchAuth = await tester.getSubject('search'); - searchAuth.authenticate('unlimited-static-token'); + await searchAuth.authenticate('unlimited-static-token'); expect(customLogic).toHaveBeenCalledWith('unlimited-static-token'); }); }); + describe('add custom ExternalTokenHandler', () => { + it('should allow custom logic to be injected into the plugin token handler', async () => { + const customLogic = jest.fn(); + const customConfig = jest.fn(); + const deps = [ + discoveryServiceFactory, + mockServices.rootConfig.factory({ + data: { + backend: { + baseUrl: 'http://localhost', + auth: { + keys: [{ secret: 'abc' }], + externalAccess: [ + { + type: 'static', + options: { + token: 'limited-static-token', + subject: 'limited-static-subject', + }, + accessRestrictions: [ + { plugin: 'catalog', permission: 'do.it' }, + ], + }, + { + type: 'static', + options: { + token: 'unlimited-static-token', + subject: 'unlimited-static-subject', + }, + }, + { + type: 'custom', + options: { + [`custom-config`]: 'custom-config', + foo: 'bar', + }, + }, + ], + }, + }, + }, + }), + ]; + + const customExternalTokenHandler = createExternalTokenHandler<{ + [`custom-config`]: string; + foo: string; + }>({ + type: 'custom', + initialize({ options }) { + const customConfigValue = options.getString('custom-config'); + const foo = options.getString('foo'); + customConfig(customConfigValue); + return { [`custom-config`]: customConfigValue, foo }; + }, + async verifyToken(token, ctx) { + customLogic(token); + return { + subject: ctx.foo, + }; + }, + }); + + const customPluginTokenHandler = createServiceFactory({ + service: externalTokenHandlersServiceRef, + deps: {}, + async factory() { + return customExternalTokenHandler; + }, + }); + const tester = ServiceFactoryTester.from(authServiceFactory, { + dependencies: [...deps, customPluginTokenHandler], + }); + const searchAuth = await tester.getSubject('search'); + await searchAuth.authenticate('custom-token'); + + expect(customLogic).toHaveBeenCalledWith('custom-token'); + expect(customConfig).toHaveBeenCalledWith('custom-config'); + }); + }); }); diff --git a/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.ts b/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.ts index 91910292e6..ee171aef25 100644 --- a/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.ts +++ b/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.ts @@ -20,7 +20,10 @@ import { createServiceRef, } from '@backstage/backend-plugin-api'; import { DefaultAuthService } from './DefaultAuthService'; -import { ExternalTokenHandler } from './external/ExternalTokenHandler'; +import { + ExternalAuthTokenHandler, + externalTokenHandlersServiceRef, +} from './external/ExternalAuthTokenHandler'; import { DefaultPluginTokenHandler, PluginTokenHandler, @@ -64,6 +67,7 @@ export const authServiceFactory = createServiceFactory({ plugin: coreServices.pluginMetadata, database: coreServices.database, pluginTokenHandlerDecorator: pluginTokenHandlerDecoratorServiceRef, + externalTokenHandlers: externalTokenHandlersServiceRef, }, async factory({ config, @@ -72,6 +76,7 @@ export const authServiceFactory = createServiceFactory({ logger, database, pluginTokenHandlerDecorator, + externalTokenHandlers, }) { const disableDefaultAuthPolicy = config.getOptionalBoolean( @@ -102,10 +107,11 @@ export const authServiceFactory = createServiceFactory({ }), ); - const externalTokens = ExternalTokenHandler.create({ + const externalTokens = ExternalAuthTokenHandler.create({ ownPluginId: plugin.getId(), config, logger, + externalTokenHandlers, }); return new DefaultAuthService( diff --git a/packages/backend-defaults/src/entrypoints/auth/external/ExternalAuthTokenHandler.test.ts b/packages/backend-defaults/src/entrypoints/auth/external/ExternalAuthTokenHandler.test.ts new file mode 100644 index 0000000000..e3f1acf270 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/auth/external/ExternalAuthTokenHandler.test.ts @@ -0,0 +1,501 @@ +/* + * 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 { BackstagePrincipalAccessRestrictions } from '@backstage/backend-plugin-api'; +import { ExternalAuthTokenHandler } from './ExternalAuthTokenHandler'; +import { createExternalTokenHandler } from './helpers'; +import { AccessRestrictionsMap, ExternalTokenHandler } from './types'; +import { + mockServices, + registerMswTestHooks, +} from '@backstage/backend-test-utils'; +import { randomBytes } from 'crypto'; +import { SignJWT, exportJWK, generateKeyPair } from 'jose'; +import { DateTime } from 'luxon'; +import { v4 as uuid } from 'uuid'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; + +// Simplified copy of TokenFactory in @backstage/plugin-auth-backend +interface AnyJWK extends Record { + use: 'sig'; + alg: string; + kid: string; + kty: string; +} +class FakeTokenFactory { + private readonly keys = new Array(); + + constructor( + private readonly options: { + issuer: string; + keyDurationSeconds: number; + }, + ) {} + + async issueToken(params: { + claims: { + sub: string; + ent?: string[]; + }; + }): Promise { + const pair = await generateKeyPair('RS256'); + const publicKey = await exportJWK(pair.publicKey); + const kid = uuid(); + publicKey.kid = kid; + this.keys.push(publicKey as AnyJWK); + + const iss = this.options.issuer; + const sub = params.claims.sub; + const ent = params.claims.ent; + const aud = 'backstage'; + const iat = Math.floor(Date.now() / 1000); + const exp = iat + this.options.keyDurationSeconds; + + return new SignJWT({ iss, sub, aud, iat, exp, ent, kid }) + .setProtectedHeader({ alg: 'RS256', ent: ent, kid: kid }) + .setIssuer(iss) + .setAudience(aud) + .setSubject(sub) + .setIssuedAt(iat) + .setExpirationTime(exp) + .sign(pair.privateKey); + } + + async listPublicKeys(): Promise<{ keys: AnyJWK[] }> { + return { keys: this.keys }; + } +} + +describe('ExternalTokenHandler', () => { + const server = setupServer(); + registerMswTestHooks(server); + + it('skips over inner handlers that do not match, and applies plugin restrictions', async () => { + const handler1: ExternalTokenHandler = createExternalTokenHandler({ + type: 'type1', + initialize: jest.fn().mockResolvedValue(undefined), + verifyToken: jest.fn().mockResolvedValue(undefined), + }); + + const handler2: ExternalTokenHandler = + createExternalTokenHandler({ + type: 'type2', + initialize: jest.fn().mockResolvedValue(undefined), + verifyToken: jest.fn().mockResolvedValue({ + subject: 'sub', + }), + }); + + const accessRestrictions: AccessRestrictionsMap = new Map( + Object.entries({ + plugin1: { + permissionNames: ['do.it'], + } satisfies BackstagePrincipalAccessRestrictions, + }), + ); + + const plugin1 = new ExternalAuthTokenHandler('plugin1', [ + { + context: undefined, + handler: handler1, + }, + { + context: undefined, + handler: handler2, + allAccessRestrictions: accessRestrictions, + }, + ]); + const plugin2 = new ExternalAuthTokenHandler('plugin2', [ + { + context: undefined, + handler: handler1, + }, + { + context: undefined, + handler: handler2, + allAccessRestrictions: accessRestrictions, + }, + ]); + + await expect(plugin1.verifyToken('token')).resolves.toEqual({ + subject: 'sub', + accessRestrictions: { permissionNames: ['do.it'] }, + }); + await expect( + plugin2.verifyToken('token'), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"This token's access is restricted to plugin(s) 'plugin1'"`, + ); + }); + + it('successfully parses known methods', async () => { + const legacyKey = randomBytes(24); + + const factory = new FakeTokenFactory({ + issuer: 'blah', + keyDurationSeconds: 100, + }); + + server.use( + rest.get( + 'https://example.com/.well-known/jwks.json', + async (_, res, ctx) => { + const keys = await factory.listPublicKeys(); + return res(ctx.json(keys)); + }, + ), + ); + + const handler = ExternalAuthTokenHandler.create({ + ownPluginId: 'catalog', + logger: mockServices.logger.mock(), + config: mockServices.rootConfig({ + data: { + backend: { + auth: { + externalAccess: [ + { + type: 'legacy', + options: { + secret: legacyKey.toString('base64'), + subject: 'legacy-subject', + }, + accessRestrictions: [ + { plugin: 'catalog', permission: 'catalog.entity.read' }, + ], + }, + { + type: 'static', + options: { + token: 'defdefdef', + subject: 'static-subject', + }, + accessRestrictions: [ + { plugin: 'catalog', permission: 'catalog.entity.read' }, + ], + }, + { + type: 'jwks', + options: { + url: 'https://example.com/.well-known/jwks.json', + algorithm: 'RS256', + issuer: 'blah', + audience: 'backstage', + subjectPrefix: 'custom-prefix', + }, + accessRestrictions: [ + { plugin: 'catalog', permission: 'catalog.entity.read' }, + ], + }, + ], + }, + }, + }, + }), + }); + + const legacyToken = await new SignJWT({ + sub: 'backstage-server', + exp: DateTime.now().plus({ minutes: 1 }).toUnixInteger(), + }) + .setProtectedHeader({ alg: 'HS256' }) + .sign(legacyKey); + + await expect(handler.verifyToken(legacyToken)).resolves.toEqual({ + subject: 'legacy-subject', + accessRestrictions: { permissionNames: ['catalog.entity.read'] }, + }); + + await expect(handler.verifyToken('defdefdef')).resolves.toEqual({ + subject: 'static-subject', + accessRestrictions: { permissionNames: ['catalog.entity.read'] }, + }); + + const jwksToken = await factory.issueToken({ + claims: { sub: 'jwks-subject' }, + }); + await expect(handler.verifyToken(jwksToken)).resolves.toEqual({ + subject: 'external:custom-prefix:jwks-subject', + accessRestrictions: { permissionNames: ['catalog.entity.read'] }, + }); + }); + it('successfully uses legacy configs', async () => { + const legacyKey = randomBytes(24); + const factory = new FakeTokenFactory({ + issuer: 'my-company', + keyDurationSeconds: 100, + }); + + server.use( + rest.get( + 'https://example.com/.well-known/jwks.json', + async (_, res, ctx) => { + const keys = await factory.listPublicKeys(); + return res(ctx.json(keys)); + }, + ), + ); + + const logger = mockServices.logger.mock(); + const handler = ExternalAuthTokenHandler.create({ + ownPluginId: 'catalog', + logger, + config: mockServices.rootConfig({ + data: { + backend: { + auth: { + keys: [ + { + secret: legacyKey.toString('base64'), + }, + ], + }, + }, + }, + }), + }); + + expect(logger.warn).toHaveBeenCalledWith( + `DEPRECATION WARNING: The backend.auth.keys config has been replaced by backend.auth.externalAccess, see https://backstage.io/docs/auth/service-to-service-auth`, + ); + + const legacyToken = await new SignJWT({ + sub: 'backstage-server', + exp: DateTime.now().plus({ minutes: 1 }).toUnixInteger(), + }) + .setProtectedHeader({ alg: 'HS256' }) + .sign(legacyKey); + + await expect(handler.verifyToken(legacyToken)).resolves.toEqual({ + subject: 'external:backstage-plugin', + }); + }); + it('successfully uses custom token handlers', async () => { + const factory = new FakeTokenFactory({ + issuer: 'my-company', + keyDurationSeconds: 100, + }); + + server.use( + rest.get( + 'https://example.com/.well-known/jwks.json', + async (_, res, ctx) => { + const keys = await factory.listPublicKeys(); + return res(ctx.json(keys)); + }, + ), + ); + + const verifyMock = jest.fn().mockResolvedValue({}); + const initializeMock = jest.fn().mockReturnValue({ context: 'a' }); + + const handler = ExternalAuthTokenHandler.create({ + ownPluginId: 'catalog', + logger: mockServices.logger.mock(), + config: mockServices.rootConfig({ + data: { + backend: { + auth: { + externalAccess: [ + { + type: 'internal-custom', + options: { + issuer: 'my-company', + subject: 'internal-subject', + audience: 'backstage', + }, + accessRestrictions: [ + { plugin: 'catalog', permission: 'catalog.entity.read' }, + ], + }, + ], + }, + }, + }, + }), + externalTokenHandlers: [ + createExternalTokenHandler({ + type: 'internal-custom', + initialize: initializeMock, + verifyToken: verifyMock, + }), + ], + }); + + expect(initializeMock).toHaveBeenCalledWith({ + options: expect.objectContaining({ + data: { + issuer: 'my-company', + subject: 'internal-subject', + audience: 'backstage', + }, + }), + }); + + const customToken = await factory.issueToken({ + claims: { sub: 'internal-subject' }, + }); + + await handler.verifyToken(customToken); + + expect(verifyMock).toHaveBeenCalledWith(customToken, { context: 'a' }); + }); + it('should fail if custom handler has same type as builtin handlers', async () => { + const logger = mockServices.logger.mock(); + const customStaticHandler: ExternalTokenHandler = + createExternalTokenHandler({ + type: 'static', + initialize: jest.fn().mockResolvedValue(undefined), + verifyToken: jest.fn().mockResolvedValue({ + subject: 'custom-static-subject', + }), + }); + + const createHandler = () => + ExternalAuthTokenHandler.create({ + ownPluginId: 'catalog', + logger, + config: mockServices.rootConfig({ + data: { + backend: { + auth: { + externalAccess: [ + { + type: 'static', + options: { + token: 'mytoken', + subject: 'static-subject', + }, + accessRestrictions: [ + { plugin: 'catalog', permission: 'catalog.entity.read' }, + ], + }, + ], + }, + }, + }, + }), + externalTokenHandlers: [customStaticHandler], + }); + + expect(createHandler).toThrowErrorMatchingInlineSnapshot( + `"Duplicate external token handler type 'static', each handler must have a unique type"`, + ); + }); + it('should fail if 2 custom handlers have the same type', async () => { + const createHandler = () => + ExternalAuthTokenHandler.create({ + ownPluginId: 'catalog', + logger: mockServices.logger.mock(), + config: mockServices.rootConfig(), + externalTokenHandlers: [ + createExternalTokenHandler({ + type: 'internal-custom', + initialize: jest.fn().mockResolvedValue(undefined), + verifyToken: jest.fn().mockResolvedValue({ + subject: 'sub', + }), + }), + createExternalTokenHandler({ + type: 'internal-custom', + initialize: jest.fn().mockResolvedValue(undefined), + verifyToken: jest.fn().mockResolvedValue({ + subject: 'sub', + }), + }), + ], + }); + + expect(createHandler).toThrowErrorMatchingInlineSnapshot( + `"Duplicate external token handler type 'internal-custom', each handler must have a unique type"`, + ); + }); + it('should fail if config contains types not declared', async () => { + const createHandler = () => + ExternalAuthTokenHandler.create({ + ownPluginId: 'catalog', + logger: mockServices.logger.mock(), + config: mockServices.rootConfig({ + data: { + backend: { + auth: { + externalAccess: [ + { + type: 'internal-custom', + options: { + issuer: 'my-company', + subject: 'internal-subject', + audience: 'backstage', + }, + accessRestrictions: [ + { plugin: 'catalog', permission: 'catalog.entity.read' }, + ], + }, + ], + }, + }, + }, + }), + }); + + expect(createHandler).toThrowErrorMatchingInlineSnapshot( + `"Unknown type 'internal-custom' in backend.auth.externalAccess, expected one of 'static', 'legacy', 'jwks'"`, + ); + }); + + it('should show valid custom types in errors', async () => { + const createHandler = () => + ExternalAuthTokenHandler.create({ + ownPluginId: 'catalog', + logger: mockServices.logger.mock(), + config: mockServices.rootConfig({ + data: { + backend: { + auth: { + externalAccess: [ + { + type: 'internal-custom-invalid', + options: { + issuer: 'my-company', + subject: 'internal-subject', + audience: 'backstage', + }, + accessRestrictions: [ + { plugin: 'catalog', permission: 'catalog.entity.read' }, + ], + }, + ], + }, + }, + }, + }), + externalTokenHandlers: [ + createExternalTokenHandler({ + type: 'internal-custom', + initialize: jest.fn().mockResolvedValue(undefined), + verifyToken: jest.fn().mockResolvedValue({ + subject: 'sub', + }), + }), + ], + }); + + expect(createHandler).toThrowErrorMatchingInlineSnapshot( + `"Unknown type 'internal-custom-invalid' in backend.auth.externalAccess, expected one of 'static', 'legacy', 'jwks', 'internal-custom'"`, + ); + }); +}); diff --git a/packages/backend-defaults/src/entrypoints/auth/external/ExternalAuthTokenHandler.ts b/packages/backend-defaults/src/entrypoints/auth/external/ExternalAuthTokenHandler.ts new file mode 100644 index 0000000000..21b3aed99d --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/auth/external/ExternalAuthTokenHandler.ts @@ -0,0 +1,187 @@ +/* + * 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 { + BackstagePrincipalAccessRestrictions, + createServiceRef, + LoggerService, + RootConfigService, +} from '@backstage/backend-plugin-api'; +import { NotAllowedError } from '@backstage/errors'; +import { legacyTokenHandler } from './legacy'; +import { staticTokenHandler } from './static'; +import { jwksTokenHandler } from './jwks'; +import { AccessRestrictionsMap, ExternalTokenHandler } from './types'; +import { readAccessRestrictionsFromConfig } from './helpers'; + +const NEW_CONFIG_KEY = 'backend.auth.externalAccess'; +const OLD_CONFIG_KEY = 'backend.auth.keys'; +let loggedDeprecationWarning = false; + +/** + * @public + * This service is used to add custom handlers for external token. + */ +export const externalTokenHandlersServiceRef = createServiceRef< + ExternalTokenHandler +>({ + id: 'core.auth.externalTokenHandlers', + multiton: true, +}); + +const defaultHandlers: Record> = { + static: staticTokenHandler, + legacy: legacyTokenHandler, + jwks: jwksTokenHandler, +}; + +type ContextMapEntry = { + context: T; + handler: ExternalTokenHandler; + allAccessRestrictions?: AccessRestrictionsMap; +}; + +/** + * Handles all types of external caller token types (i.e. not Backstage user + * tokens, nor Backstage backend plugin tokens). + * + * @internal + */ +export class ExternalAuthTokenHandler { + static create(options: { + ownPluginId: string; + config: RootConfigService; + logger: LoggerService; + externalTokenHandlers?: ExternalTokenHandler[]; + }): ExternalAuthTokenHandler { + const { + ownPluginId, + config, + externalTokenHandlers: customHandlers = [], + logger, + } = options; + + const handlersTypes = customHandlers.reduce< + Record> + >( + (acc, handler) => { + if (acc[handler.type]) { + throw new Error( + `Duplicate external token handler type '${handler.type}', each handler must have a unique type`, + ); + } + acc[handler.type] = handler; + return acc; + }, + { ...defaultHandlers }, + ); + + const handlerConfigs = config.getOptionalConfigArray(NEW_CONFIG_KEY) ?? []; + const contexts: ContextMapEntry[] = handlerConfigs.map( + handlerConfig => { + const type = handlerConfig.getString('type'); + + const handler = handlersTypes[type]; + if (!handler) { + const valid = Object.keys(handlersTypes) + .map(h => `'${h}'`) + .join(', '); + throw new Error( + `Unknown type '${type}' in ${NEW_CONFIG_KEY}, expected one of ${valid}`, + ); + } + return { + context: handler.initialize({ + options: handlerConfig.getConfig('options'), + }), + handler, + allAccessRestrictions: handlerConfig + ? readAccessRestrictionsFromConfig(handlerConfig) + : undefined, + }; + }, + ); + + // Load the old keys too + const legacyConfigs = config.getOptionalConfigArray(OLD_CONFIG_KEY) ?? []; + + if (legacyConfigs.length && !loggedDeprecationWarning) { + loggedDeprecationWarning = true; + + logger.warn( + `DEPRECATION WARNING: The ${OLD_CONFIG_KEY} config has been replaced by ${NEW_CONFIG_KEY}, see https://backstage.io/docs/auth/service-to-service-auth`, + ); + } + + for (const legacyConfig of legacyConfigs) { + contexts.push({ + context: legacyTokenHandler.initialize({ + legacy: true, + options: legacyConfig, + }), + handler: legacyTokenHandler, + allAccessRestrictions: readAccessRestrictionsFromConfig(legacyConfig), + }); + } + + return new ExternalAuthTokenHandler(ownPluginId, contexts); + } + + constructor( + private readonly ownPluginId: string, + private readonly contexts: { + context: unknown; + handler: ExternalTokenHandler; + allAccessRestrictions?: AccessRestrictionsMap; + }[], + ) {} + + async verifyToken(token: string): Promise< + | { + subject: string; + accessRestrictions?: BackstagePrincipalAccessRestrictions; + } + | undefined + > { + for (const { handler, allAccessRestrictions, context } of this.contexts) { + const result = await handler.verifyToken(token, context); + if (result) { + if (allAccessRestrictions) { + const accessRestrictions = allAccessRestrictions.get( + this.ownPluginId, + ); + if (!accessRestrictions) { + const valid = [...allAccessRestrictions.keys()] + .map(k => `'${k}'`) + .join(', '); + throw new NotAllowedError( + `This token's access is restricted to plugin(s) ${valid}`, + ); + } + + return { + ...result, + accessRestrictions, + }; + } + + return result; + } + } + + return undefined; + } +} diff --git a/packages/backend-defaults/src/entrypoints/auth/external/ExternalTokenHandler.test.ts b/packages/backend-defaults/src/entrypoints/auth/external/ExternalTokenHandler.test.ts deleted file mode 100644 index 39b01c46b6..0000000000 --- a/packages/backend-defaults/src/entrypoints/auth/external/ExternalTokenHandler.test.ts +++ /dev/null @@ -1,211 +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 { BackstagePrincipalAccessRestrictions } from '@backstage/backend-plugin-api'; -import { ExternalTokenHandler } from './ExternalTokenHandler'; -import { TokenHandler } from './types'; -import { - mockServices, - registerMswTestHooks, -} from '@backstage/backend-test-utils'; -import { randomBytes } from 'crypto'; -import { SignJWT, exportJWK, generateKeyPair } from 'jose'; -import { DateTime } from 'luxon'; -import { v4 as uuid } from 'uuid'; -import { rest } from 'msw'; -import { setupServer } from 'msw/node'; - -// Simplified copy of TokenFactory in @backstage/plugin-auth-backend -interface AnyJWK extends Record { - use: 'sig'; - alg: string; - kid: string; - kty: string; -} -class FakeTokenFactory { - private readonly keys = new Array(); - - constructor( - private readonly options: { - issuer: string; - keyDurationSeconds: number; - }, - ) {} - - async issueToken(params: { - claims: { - sub: string; - ent?: string[]; - }; - }): Promise { - const pair = await generateKeyPair('RS256'); - const publicKey = await exportJWK(pair.publicKey); - const kid = uuid(); - publicKey.kid = kid; - this.keys.push(publicKey as AnyJWK); - - const iss = this.options.issuer; - const sub = params.claims.sub; - const ent = params.claims.ent; - const aud = 'backstage'; - const iat = Math.floor(Date.now() / 1000); - const exp = iat + this.options.keyDurationSeconds; - - return new SignJWT({ iss, sub, aud, iat, exp, ent, kid }) - .setProtectedHeader({ alg: 'RS256', ent: ent, kid: kid }) - .setIssuer(iss) - .setAudience(aud) - .setSubject(sub) - .setIssuedAt(iat) - .setExpirationTime(exp) - .sign(pair.privateKey); - } - - async listPublicKeys(): Promise<{ keys: AnyJWK[] }> { - return { keys: this.keys }; - } -} - -describe('ExternalTokenHandler', () => { - const server = setupServer(); - registerMswTestHooks(server); - - it('skips over inner handlers that do not match, and applies plugin restrictions', async () => { - const handler1: TokenHandler = { - add: jest.fn(), - verifyToken: jest.fn().mockResolvedValue(undefined), - }; - - const handler2: TokenHandler = { - add: jest.fn(), - verifyToken: jest.fn().mockResolvedValue({ - subject: 'sub', - allAccessRestrictions: new Map( - Object.entries({ - plugin1: { - permissionNames: ['do.it'], - } satisfies BackstagePrincipalAccessRestrictions, - }), - ), - }), - }; - - const plugin1 = new ExternalTokenHandler('plugin1', [handler1, handler2]); - const plugin2 = new ExternalTokenHandler('plugin2', [handler1, handler2]); - - await expect(plugin1.verifyToken('token')).resolves.toEqual({ - subject: 'sub', - accessRestrictions: { permissionNames: ['do.it'] }, - }); - await expect( - plugin2.verifyToken('token'), - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"This token's access is restricted to plugin(s) 'plugin1'"`, - ); - }); - - it('successfully parses known methods', async () => { - const legacyKey = randomBytes(24); - - const factory = new FakeTokenFactory({ - issuer: 'blah', - keyDurationSeconds: 100, - }); - - server.use( - rest.get( - 'https://example.com/.well-known/jwks.json', - async (_, res, ctx) => { - const keys = await factory.listPublicKeys(); - return res(ctx.json(keys)); - }, - ), - ); - - const handler = ExternalTokenHandler.create({ - ownPluginId: 'catalog', - logger: mockServices.logger.mock(), - config: mockServices.rootConfig({ - data: { - backend: { - auth: { - externalAccess: [ - { - type: 'legacy', - options: { - secret: legacyKey.toString('base64'), - subject: 'legacy-subject', - }, - accessRestrictions: [ - { plugin: 'catalog', permission: 'catalog.entity.read' }, - ], - }, - { - type: 'static', - options: { - token: 'defdefdef', - subject: 'static-subject', - }, - accessRestrictions: [ - { plugin: 'catalog', permission: 'catalog.entity.read' }, - ], - }, - { - type: 'jwks', - options: { - url: 'https://example.com/.well-known/jwks.json', - algorithm: 'RS256', - issuer: 'blah', - audience: 'backstage', - subjectPrefix: 'custom-prefix', - }, - accessRestrictions: [ - { plugin: 'catalog', permission: 'catalog.entity.read' }, - ], - }, - ], - }, - }, - }, - }), - }); - - const legacyToken = await new SignJWT({ - sub: 'backstage-server', - exp: DateTime.now().plus({ minutes: 1 }).toUnixInteger(), - }) - .setProtectedHeader({ alg: 'HS256' }) - .sign(legacyKey); - - await expect(handler.verifyToken(legacyToken)).resolves.toEqual({ - subject: 'legacy-subject', - accessRestrictions: { permissionNames: ['catalog.entity.read'] }, - }); - - await expect(handler.verifyToken('defdefdef')).resolves.toEqual({ - subject: 'static-subject', - accessRestrictions: { permissionNames: ['catalog.entity.read'] }, - }); - - const jwksToken = await factory.issueToken({ - claims: { sub: 'jwks-subject' }, - }); - await expect(handler.verifyToken(jwksToken)).resolves.toEqual({ - subject: 'external:custom-prefix:jwks-subject', - accessRestrictions: { permissionNames: ['catalog.entity.read'] }, - }); - }); -}); diff --git a/packages/backend-defaults/src/entrypoints/auth/external/ExternalTokenHandler.ts b/packages/backend-defaults/src/entrypoints/auth/external/ExternalTokenHandler.ts deleted file mode 100644 index 72eeae6fcf..0000000000 --- a/packages/backend-defaults/src/entrypoints/auth/external/ExternalTokenHandler.ts +++ /dev/null @@ -1,127 +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 { - BackstagePrincipalAccessRestrictions, - LoggerService, - RootConfigService, -} from '@backstage/backend-plugin-api'; -import { NotAllowedError } from '@backstage/errors'; -import { LegacyTokenHandler } from './legacy'; -import { StaticTokenHandler } from './static'; -import { JWKSHandler } from './jwks'; -import { TokenHandler } from './types'; - -const NEW_CONFIG_KEY = 'backend.auth.externalAccess'; -const OLD_CONFIG_KEY = 'backend.auth.keys'; -let loggedDeprecationWarning = false; - -/** - * Handles all types of external caller token types (i.e. not Backstage user - * tokens, nor Backstage backend plugin tokens). - * - * @internal - */ -export class ExternalTokenHandler { - static create(options: { - ownPluginId: string; - config: RootConfigService; - logger: LoggerService; - }): ExternalTokenHandler { - const { ownPluginId, config, logger } = options; - - const staticHandler = new StaticTokenHandler(); - const legacyHandler = new LegacyTokenHandler(); - const jwksHandler = new JWKSHandler(); - const handlers: Record = { - static: staticHandler, - legacy: legacyHandler, - jwks: jwksHandler, - }; - - // Load the new-style handlers - const handlerConfigs = config.getOptionalConfigArray(NEW_CONFIG_KEY) ?? []; - for (const handlerConfig of handlerConfigs) { - const type = handlerConfig.getString('type'); - const handler = handlers[type]; - if (!handler) { - const valid = Object.keys(handlers) - .map(k => `'${k}'`) - .join(', '); - throw new Error( - `Unknown type '${type}' in ${NEW_CONFIG_KEY}, expected one of ${valid}`, - ); - } - handler.add(handlerConfig); - } - - // Load the old keys too - const legacyConfigs = config.getOptionalConfigArray(OLD_CONFIG_KEY) ?? []; - if (legacyConfigs.length && !loggedDeprecationWarning) { - loggedDeprecationWarning = true; - logger.warn( - `DEPRECATION WARNING: The ${OLD_CONFIG_KEY} config has been replaced by ${NEW_CONFIG_KEY}, see https://backstage.io/docs/auth/service-to-service-auth`, - ); - } - for (const handlerConfig of legacyConfigs) { - legacyHandler.addOld(handlerConfig); - } - - return new ExternalTokenHandler(ownPluginId, Object.values(handlers)); - } - - constructor( - private readonly ownPluginId: string, - private readonly handlers: TokenHandler[], - ) {} - - async verifyToken(token: string): Promise< - | { - subject: string; - accessRestrictions?: BackstagePrincipalAccessRestrictions; - } - | undefined - > { - for (const handler of this.handlers) { - const result = await handler.verifyToken(token); - if (result) { - const { allAccessRestrictions, ...rest } = result; - if (allAccessRestrictions) { - const accessRestrictions = allAccessRestrictions.get( - this.ownPluginId, - ); - if (!accessRestrictions) { - const valid = [...allAccessRestrictions.keys()] - .map(k => `'${k}'`) - .join(', '); - throw new NotAllowedError( - `This token's access is restricted to plugin(s) ${valid}`, - ); - } - - return { - ...rest, - accessRestrictions, - }; - } - - return rest; - } - } - - return undefined; - } -} diff --git a/packages/backend-defaults/src/entrypoints/auth/external/helpers.ts b/packages/backend-defaults/src/entrypoints/auth/external/helpers.ts index d6aa0a01ff..94d289bd7c 100644 --- a/packages/backend-defaults/src/entrypoints/auth/external/helpers.ts +++ b/packages/backend-defaults/src/entrypoints/auth/external/helpers.ts @@ -15,7 +15,7 @@ */ import { Config } from '@backstage/config'; -import { AccessRestriptionsMap } from './types'; +import { AccessRestrictionsMap, ExternalTokenHandler } from './types'; /** * Parses and returns the `accessRestrictions` configuration from an @@ -25,12 +25,12 @@ import { AccessRestriptionsMap } from './types'; */ export function readAccessRestrictionsFromConfig( externalAccessEntryConfig: Config, -): AccessRestriptionsMap | undefined { +): AccessRestrictionsMap | undefined { const configs = externalAccessEntryConfig.getOptionalConfigArray('accessRestrictions') ?? []; - const result: AccessRestriptionsMap = new Map(); + const result: AccessRestrictionsMap = new Map(); for (const config of configs) { const validKeys = ['plugin', 'permission', 'permissionAttribute']; for (const key of config.keys()) { @@ -147,3 +147,37 @@ function readPermissionAttributes(externalAccessEntryConfig: Config) { return Object.keys(result).length ? result : undefined; } + +/** + * Creates an external token handler with the provided implementation. + * + * This helper function simplifies the creation of external token handlers by + * providing type safety and a consistent API. External token handlers are used + * to validate tokens from external systems that need to authenticate with Backstage. + * + * See {@link https://backstage.io/docs/auth/service-to-service-auth#adding-custom-externaltokenhandler | the service-to-service auth docs} + * for more information about implementing custom external token handlers. + * + * @public + * @param handler - The external token handler implementation with type, initialize, and verifyToken methods + * @returns The same handler instance, typed as ExternalTokenHandler + * + * @example + * ```ts + * const customHandler = createExternalTokenHandler({ + * type: 'custom', + * initialize({ options }) { + * return { apiKey: options.getString('apiKey') }; + * }, + * async verifyToken(token, context) { + * // Custom validation logic here + * return { subject: 'custom:user' }; + * }, + * }); + * ``` + */ +export function createExternalTokenHandler( + handler: ExternalTokenHandler, +): ExternalTokenHandler { + return handler; +} diff --git a/packages/backend-defaults/src/entrypoints/auth/external/jwks.test.ts b/packages/backend-defaults/src/entrypoints/auth/external/jwks.test.ts index 321e1823aa..778e5090bf 100644 --- a/packages/backend-defaults/src/entrypoints/auth/external/jwks.test.ts +++ b/packages/backend-defaults/src/entrypoints/auth/external/jwks.test.ts @@ -20,7 +20,7 @@ import { SignJWT, exportJWK, generateKeyPair } from 'jose'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; import { v4 as uuid } from 'uuid'; -import { JWKSHandler } from './jwks'; +import { jwksTokenHandler } from './jwks'; // Simplified copy of TokenFactory in @backstage/plugin-auth-backend interface AnyJWK extends Record { @@ -101,114 +101,45 @@ describe('JWKSHandler', () => { it('verifies token with valid entry', async () => { const validEntry = { - options: { - url: `${mockBaseUrl}/.well-known/jwks.json`, - algorithm: 'RS256', - issuer: mockBaseUrl, - audience: 'backstage', - }, + url: `${mockBaseUrl}/.well-known/jwks.json`, + algorithm: 'RS256', + issuer: mockBaseUrl, + audience: 'backstage', }; - const jwksHandler = new JWKSHandler(); - - jwksHandler.add(new ConfigReader(validEntry)); + const context = jwksTokenHandler.initialize({ + options: new ConfigReader(validEntry), + }); const token = await factory.issueToken({ claims: { sub: mockSubject }, }); - const result = await jwksHandler.verifyToken(token); + const result = await jwksTokenHandler.verifyToken(token, context); expect(result).toEqual({ subject: `external:${mockSubject}` }); }); - it('skips invalid entry and continues verification', async () => { - const invalidEntry = { - options: { - url: `${mockBaseUrl}/.well-known/jwks.json`, - algorithm: 'RS256', - issuer: ['fakeIssuer'], - audience: ['fakeAud'], - }, - }; - - const validEntry = { - options: { - url: `${mockBaseUrl}/.well-known/jwks.json`, - algorithm: 'RS256', - issuer: ['multiple-issuers', mockBaseUrl], - audience: ['multiple-audiences', 'backstage'], - }, - }; - const jwksHandler = new JWKSHandler(); - - jwksHandler.add(new ConfigReader(invalidEntry)); - jwksHandler.add(new ConfigReader(validEntry)); - - const token = await factory.issueToken({ - claims: { sub: mockSubject }, - }); - - const result = await jwksHandler.verifyToken(token); - - expect(result).toEqual({ subject: `external:${mockSubject}` }); - }); - - it('returns undefined if no valid entry found', async () => { - const invalidEntry1 = { - options: { - url: `${mockBaseUrl}/.well-known/jwks.json`, - algorithm: 'RS256', - issuer: 'wrong', - }, - }; - - const invalidEntry2 = { - options: { - url: `${mockBaseUrl}/.well-known/jwks.json`, - algorithm: ['HS256'], - audience: 'wrong', - }, - }; - const jwksHandler = new JWKSHandler(); - - jwksHandler.add(new ConfigReader(invalidEntry1)); - jwksHandler.add(new ConfigReader(invalidEntry2)); - - const token = await factory.issueToken({ - claims: { sub: mockSubject }, - }); - - const result = await jwksHandler.verifyToken(token); - - expect(result).toBeUndefined(); - }); - it('rejects bad config', () => { - const jwksHandler = new JWKSHandler(); - expect(() => { - jwksHandler.add( - new ConfigReader({ - options: { - url: 'https://exampl e.com/jwks', - }, + return jwksTokenHandler.initialize({ + options: new ConfigReader({ + url: 'https://exampl e.com/jwks', }), - ); + }); }).toThrow('Illegal JWKS URL, must be a set of non-space characters'); expect(() => { - jwksHandler.add( - new ConfigReader({ - options: { - url: 'https://example.com/jwks\n', - }, + return jwksTokenHandler.initialize({ + options: new ConfigReader({ + url: 'https://example.com/jwks\n', }), - ); + }); }).toThrow('Illegal JWKS URL, must be a set of non-space characters'); }); it('gracefully handles no added tokens', async () => { - const handler = new JWKSHandler(); - await expect(handler.verifyToken('ghi')).resolves.toBeUndefined(); + await expect( + jwksTokenHandler.verifyToken('ghi', {} as any), + ).resolves.toBeUndefined(); }); it('uses custom subject prefix if provided', async () => { @@ -221,41 +152,18 @@ describe('JWKSHandler', () => { subjectPrefix: 'custom-prefix', }, }; - const jwksHandler = new JWKSHandler(); - - jwksHandler.add(new ConfigReader(validEntry)); + const context = jwksTokenHandler.initialize({ + options: new ConfigReader(validEntry.options), + }); const token = await factory.issueToken({ claims: { sub: mockSubject }, }); - const result = await jwksHandler.verifyToken(token); + const result = await jwksTokenHandler.verifyToken(token, context); expect(result).toEqual({ subject: `external:${validEntry.options.subjectPrefix}:${mockSubject}`, }); }); - - it('carries over access restrictions', async () => { - const jwksHandler = new JWKSHandler(); - jwksHandler.add( - new ConfigReader({ - options: { - url: `${mockBaseUrl}/.well-known/jwks.json`, - }, - accessRestrictions: [{ plugin: 'scaffolder', permission: 'do.it' }], - }), - ); - - const token = await factory.issueToken({ claims: { sub: mockSubject } }); - - await expect(jwksHandler.verifyToken(token)).resolves.toEqual({ - subject: `external:${mockSubject}`, - allAccessRestrictions: new Map( - Object.entries({ - scaffolder: { permissionNames: ['do.it'] }, - }), - ), - }); - }); }); diff --git a/packages/backend-defaults/src/entrypoints/auth/external/jwks.ts b/packages/backend-defaults/src/entrypoints/auth/external/jwks.ts index d88dc62a47..33b55df775 100644 --- a/packages/backend-defaults/src/entrypoints/auth/external/jwks.ts +++ b/packages/backend-defaults/src/entrypoints/auth/external/jwks.ts @@ -15,51 +15,40 @@ */ import { jwtVerify, createRemoteJWKSet, JWTVerifyGetKey } from 'jose'; -import { Config } from '@backstage/config'; import { + createExternalTokenHandler, readAccessRestrictionsFromConfig, readStringOrStringArrayFromConfig, } from './helpers'; -import { AccessRestriptionsMap, TokenHandler } from './types'; +import { AccessRestrictionsMap } from './types'; -/** - * Handles `type: jwks` access. - * - * @internal - */ -export class JWKSHandler implements TokenHandler { - #entries: Array<{ - algorithms?: string[]; - audiences?: string[]; - issuers?: string[]; - subjectPrefix?: string; - url: URL; - jwks: JWTVerifyGetKey; - allAccessRestrictions?: AccessRestriptionsMap; - }> = []; +type JWKSTokenContext = { + algorithms?: string[]; + audiences?: string[]; + issuers?: string[]; + subjectPrefix?: string; + url: URL; + jwks: JWTVerifyGetKey; + allAccessRestrictions?: AccessRestrictionsMap; +}; - add(config: Config) { - if (!config.getString('options.url').match(/^\S+$/)) { +export const jwksTokenHandler = createExternalTokenHandler({ + type: 'jwks', + initialize({ options }): JWKSTokenContext { + if (!options.getString('url').match(/^\S+$/)) { throw new Error( 'Illegal JWKS URL, must be a set of non-space characters', ); } - const algorithms = readStringOrStringArrayFromConfig( - config, - 'options.algorithm', - ); - const issuers = readStringOrStringArrayFromConfig(config, 'options.issuer'); - const audiences = readStringOrStringArrayFromConfig( - config, - 'options.audience', - ); - const subjectPrefix = config.getOptionalString('options.subjectPrefix'); - const url = new URL(config.getString('options.url')); + const algorithms = readStringOrStringArrayFromConfig(options, 'algorithm'); + const issuers = readStringOrStringArrayFromConfig(options, 'issuer'); + const audiences = readStringOrStringArrayFromConfig(options, 'audience'); + const subjectPrefix = options.getOptionalString('subjectPrefix'); + const url = new URL(options.getString('url')); const jwks = createRemoteJWKSet(url); - const allAccessRestrictions = readAccessRestrictionsFromConfig(config); - - this.#entries.push({ + const allAccessRestrictions = readAccessRestrictionsFromConfig(options); + return { algorithms, audiences, issuers, @@ -67,33 +56,30 @@ export class JWKSHandler implements TokenHandler { subjectPrefix, url, allAccessRestrictions, - }); - } + }; + }, - async verifyToken(token: string) { - for (const entry of this.#entries) { - try { - const { - payload: { sub }, - } = await jwtVerify(token, entry.jwks, { - algorithms: entry.algorithms, - issuer: entry.issuers, - audience: entry.audiences, - }); + async verifyToken(token: string, context: JWKSTokenContext) { + try { + const { + payload: { sub }, + } = await jwtVerify(token, context.jwks, { + algorithms: context.algorithms, + issuer: context.issuers, + audience: context.audiences, + }); - if (sub) { - const prefix = entry.subjectPrefix - ? `external:${entry.subjectPrefix}:` - : 'external:'; - return { - subject: `${prefix}${sub}`, - allAccessRestrictions: entry.allAccessRestrictions, - }; - } - } catch { - continue; + if (sub) { + const prefix = context.subjectPrefix + ? `external:${context.subjectPrefix}:` + : 'external:'; + return { + subject: `${prefix}${sub}`, + }; } + } catch { + return undefined; } return undefined; - } -} + }, +}); diff --git a/packages/backend-defaults/src/entrypoints/auth/external/legacy.test.ts b/packages/backend-defaults/src/entrypoints/auth/external/legacy.test.ts index c674e46ec9..48dbcc2418 100644 --- a/packages/backend-defaults/src/entrypoints/auth/external/legacy.test.ts +++ b/packages/backend-defaults/src/entrypoints/auth/external/legacy.test.ts @@ -18,49 +18,35 @@ import { ConfigReader } from '@backstage/config'; import { randomBytes } from 'crypto'; import { SignJWT, importJWK } from 'jose'; import { DateTime } from 'luxon'; -import { LegacyTokenHandler } from './legacy'; +import { legacyTokenHandler } from './legacy'; describe('LegacyTokenHandler', () => { - const tokenHandler = new LegacyTokenHandler(); const key1 = randomBytes(24); const key2 = randomBytes(24); const key3 = randomBytes(24); - const accessRestrictions1 = new Map( - Object.entries({ - scaffolder: {}, - }), - ); - const accessRestrictions2 = new Map( - Object.entries({ - catalog: { permissionNames: ['catalog.entity.read'] }, - }), - ); - tokenHandler.add( - new ConfigReader({ - options: { - secret: key1.toString('base64'), - subject: 'key1', - }, - accessRestrictions: [{ plugin: 'scaffolder' }], + const tokenHandler = legacyTokenHandler; + + const context1 = tokenHandler.initialize({ + options: new ConfigReader({ + secret: key1.toString('base64'), + subject: 'key1', }), - ); - tokenHandler.add( - new ConfigReader({ - options: { - secret: key2.toString('base64'), - subject: 'key2', - }, - accessRestrictions: [ - { plugin: 'catalog', permission: 'catalog.entity.read' }, - ], + }); + + const context2 = tokenHandler.initialize({ + options: new ConfigReader({ + secret: key2.toString('base64'), + subject: 'key2', }), - ); - tokenHandler.addOld( - new ConfigReader({ + }); + + const contextWithLegacy = tokenHandler.initialize({ + options: new ConfigReader({ secret: key3.toString('base64'), }), - ); + legacy: true, + }); it('should verify valid tokens', async () => { const token1 = await new SignJWT({ @@ -70,9 +56,8 @@ describe('LegacyTokenHandler', () => { .setProtectedHeader({ alg: 'HS256' }) .sign(key1); - await expect(tokenHandler.verifyToken(token1)).resolves.toEqual({ + await expect(tokenHandler.verifyToken(token1, context1)).resolves.toEqual({ subject: 'key1', - allAccessRestrictions: accessRestrictions1, }); const token2 = await new SignJWT({ @@ -82,9 +67,8 @@ describe('LegacyTokenHandler', () => { .setProtectedHeader({ alg: 'HS256' }) .sign(key2); - await expect(tokenHandler.verifyToken(token2)).resolves.toEqual({ + await expect(tokenHandler.verifyToken(token2, context2)).resolves.toEqual({ subject: 'key2', - allAccessRestrictions: accessRestrictions2, }); const token3 = await new SignJWT({ @@ -94,7 +78,9 @@ describe('LegacyTokenHandler', () => { .setProtectedHeader({ alg: 'HS256' }) .sign(key3); - await expect(tokenHandler.verifyToken(token3)).resolves.toEqual({ + await expect( + tokenHandler.verifyToken(token3, contextWithLegacy), + ).resolves.toEqual({ subject: 'external:backstage-plugin', }); }); @@ -107,10 +93,18 @@ describe('LegacyTokenHandler', () => { .setProtectedHeader({ alg: 'HS256' }) .sign(key1); - await expect(tokenHandler.verifyToken(validToken)).resolves.toBeUndefined(); + await expect( + tokenHandler.verifyToken(validToken, context1), + ).resolves.toBeUndefined(); + await expect( + tokenHandler.verifyToken(validToken, context2), + ).resolves.toBeUndefined(); await expect( - tokenHandler.verifyToken('statickeyblaaa'), + tokenHandler.verifyToken('statickeyblaaa', context1), + ).resolves.toBeUndefined(); + await expect( + tokenHandler.verifyToken('statickeyblaaa', context2), ).resolves.toBeUndefined(); const randomToken = await new SignJWT({ @@ -120,7 +114,10 @@ describe('LegacyTokenHandler', () => { .setProtectedHeader({ alg: 'HS256' }) .sign(randomBytes(24)); await expect( - tokenHandler.verifyToken(randomToken), + tokenHandler.verifyToken(randomToken, context1), + ).resolves.toBeUndefined(); + await expect( + tokenHandler.verifyToken(randomToken, context2), ).resolves.toBeUndefined(); const mockPublicKey = { @@ -144,7 +141,10 @@ describe('LegacyTokenHandler', () => { .sign(await importJWK(mockPrivateKey)); await expect( - tokenHandler.verifyToken(keyWithWrongAlg), + tokenHandler.verifyToken(keyWithWrongAlg, context1), + ).resolves.toBeUndefined(); + await expect( + tokenHandler.verifyToken(keyWithWrongAlg, context2), ).resolves.toBeUndefined(); }); @@ -157,130 +157,151 @@ describe('LegacyTokenHandler', () => { .setProtectedHeader({ alg: 'HS256' }) .sign(key1); - await expect(tokenHandler.verifyToken(keyWithWrongExp)).rejects.toThrow( - /\"exp\" claim must be a number/, - ); + await expect( + tokenHandler.verifyToken(keyWithWrongExp, context1), + ).rejects.toThrow(/\"exp\" claim must be a number/); }); it('rejects bad config', () => { - const handler = new LegacyTokenHandler(); + const handler = legacyTokenHandler; // new style add, bad secrets expect(() => - handler.add( - new ConfigReader({ options: { _missingsecret: true, subject: 'ok' } }), - ), - ).toThrowErrorMatchingInlineSnapshot( - `"Missing required config value at 'options.secret' in 'mock-config'"`, - ); - expect(() => - handler.add(new ConfigReader({ options: { secret: '', subject: 'ok' } })), - ).toThrowErrorMatchingInlineSnapshot( - `"Invalid type in config for key 'options.secret' in 'mock-config', got empty-string, wanted string"`, - ); - expect(() => - handler.add( - new ConfigReader({ options: { secret: 'has spaces', subject: 'ok' } }), - ), - ).toThrowErrorMatchingInlineSnapshot( - `"Illegal secret, must be a valid base64 string"`, - ); - expect(() => - handler.add( - new ConfigReader({ - options: { secret: 'hasnewline\n', subject: 'ok' }, + handler.initialize({ + options: new ConfigReader({ + _missingsecret: true, + subject: 'ok', }), - ), - ).toThrowErrorMatchingInlineSnapshot( - `"Illegal secret, must be a valid base64 string"`, - ); - expect(() => - handler.add(new ConfigReader({ options: { secret: 3, subject: 'ok' } })), - ).toThrowErrorMatchingInlineSnapshot( - `"Invalid type in config for key 'options.secret' in 'mock-config', got number, wanted string"`, - ); - - // new style add, bad subjects - expect(() => - handler.add( - new ConfigReader({ - options: { secret: 'b2s=', _missingsubject: true }, - }), - ), - ).toThrowErrorMatchingInlineSnapshot( - `"Missing required config value at 'options.subject' in 'mock-config'"`, - ); - expect(() => - handler.add( - new ConfigReader({ options: { secret: 'b2s=', subject: '' } }), - ), - ).toThrowErrorMatchingInlineSnapshot( - `"Invalid type in config for key 'options.subject' in 'mock-config', got empty-string, wanted string"`, - ); - expect(() => - handler.add( - new ConfigReader({ - options: { secret: 'b2s=', subject: 'has spaces' }, - }), - ), - ).toThrowErrorMatchingInlineSnapshot( - `"Illegal subject, must be a set of non-space characters"`, - ); - expect(() => - handler.add( - new ConfigReader({ - options: { secret: 'b2s=', subject: 'hasnewline\n' }, - }), - ), - ).toThrowErrorMatchingInlineSnapshot( - `"Illegal subject, must be a set of non-space characters"`, - ); - expect(() => - handler.add( - new ConfigReader({ options: { secret: 'b2s=', subject: 3 } }), - ), - ).toThrowErrorMatchingInlineSnapshot( - `"Invalid type in config for key 'options.subject' in 'mock-config', got number, wanted string"`, - ); - - // new style add, bad access restrictions - expect(() => - handler.add( - new ConfigReader({ - options: { secret: 'b2s=', subject: 'subject' }, - accessRestrictions: [{ plugin: ['a'] }], - }), - ), - ).toThrowErrorMatchingInlineSnapshot( - `"Invalid type in config for key 'accessRestrictions[0].plugin' in 'mock-config', got array, wanted string"`, - ); - - // old style add - expect(() => - handler.addOld(new ConfigReader({ secret: 'b2s=' })), - ).not.toThrow(); - expect(() => - handler.addOld(new ConfigReader({ _missingsecret: true })), + }), ).toThrowErrorMatchingInlineSnapshot( `"Missing required config value at 'secret' in 'mock-config'"`, ); expect(() => - handler.addOld(new ConfigReader({ secret: '' })), + handler.initialize({ + options: new ConfigReader({ secret: '', subject: 'ok' }), + }), ).toThrowErrorMatchingInlineSnapshot( `"Invalid type in config for key 'secret' in 'mock-config', got empty-string, wanted string"`, ); expect(() => - handler.addOld(new ConfigReader({ secret: 'has spaces' })), + handler.initialize({ + options: new ConfigReader({ + secret: 'has spaces', + subject: 'ok', + }), + }), ).toThrowErrorMatchingInlineSnapshot( `"Illegal secret, must be a valid base64 string"`, ); expect(() => - handler.addOld(new ConfigReader({ secret: 'hasnewline\n' })), + handler.initialize({ + options: new ConfigReader({ + secret: 'hasnewline\n', + subject: 'ok', + }), + }), ).toThrowErrorMatchingInlineSnapshot( `"Illegal secret, must be a valid base64 string"`, ); expect(() => - handler.addOld(new ConfigReader({ secret: 3 })), + handler.initialize({ + options: new ConfigReader({ secret: 3, subject: 'ok' }), + }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'secret' in 'mock-config', got number, wanted string"`, + ); + + // new style add, bad subjects + expect(() => + handler.initialize({ + options: new ConfigReader({ + secret: 'b2s=', + _missingsubject: true, + }), + }), + ).toThrowErrorMatchingInlineSnapshot( + `"Missing required config value at 'subject' in 'mock-config'"`, + ); + expect(() => + handler.initialize({ + options: new ConfigReader({ secret: 'b2s=', subject: '' }), + }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'subject' in 'mock-config', got empty-string, wanted string"`, + ); + expect(() => + handler.initialize({ + options: new ConfigReader({ + secret: 'b2s=', + subject: 'has spaces', + }), + }), + ).toThrowErrorMatchingInlineSnapshot( + `"Illegal subject, must be a set of non-space characters"`, + ); + expect(() => + handler.initialize({ + options: new ConfigReader({ + secret: 'b2s=', + subject: 'hasnewline\n', + }), + }), + ).toThrowErrorMatchingInlineSnapshot( + `"Illegal subject, must be a set of non-space characters"`, + ); + expect(() => + handler.initialize({ + options: new ConfigReader({ secret: 'b2s=', subject: 3 }), + }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'subject' in 'mock-config', got number, wanted string"`, + ); + + // old style add + expect(() => + handler.initialize({ + options: new ConfigReader({ secret: 'b2s=' }), + legacy: true, + }), + ).not.toThrow(); + + expect(() => + handler.initialize({ + options: new ConfigReader({ _missingsecret: true }), + legacy: true, + }), + ).toThrowErrorMatchingInlineSnapshot( + `"Missing required config value at 'secret' in 'mock-config'"`, + ); + expect(() => + handler.initialize({ + options: new ConfigReader({ secret: '' }), + legacy: true, + }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'secret' in 'mock-config', got empty-string, wanted string"`, + ); + expect(() => + handler.initialize({ + options: new ConfigReader({ secret: 'has spaces' }), + legacy: true, + }), + ).toThrowErrorMatchingInlineSnapshot( + `"Illegal secret, must be a valid base64 string"`, + ); + expect(() => + handler.initialize({ + options: new ConfigReader({ secret: 'hasnewline\n' }), + legacy: true, + }), + ).toThrowErrorMatchingInlineSnapshot( + `"Illegal secret, must be a valid base64 string"`, + ); + expect(() => + handler.initialize({ + options: new ConfigReader({ secret: 3 }), + legacy: true, + }), ).toThrowErrorMatchingInlineSnapshot( `"Invalid type in config for key 'secret' in 'mock-config', got number, wanted string"`, ); diff --git a/packages/backend-defaults/src/entrypoints/auth/external/legacy.ts b/packages/backend-defaults/src/entrypoints/auth/external/legacy.ts index 9c60e70707..2ab8c3a5ea 100644 --- a/packages/backend-defaults/src/entrypoints/auth/external/legacy.ts +++ b/packages/backend-defaults/src/entrypoints/auth/external/legacy.ts @@ -16,72 +16,56 @@ import { Config } from '@backstage/config'; import { base64url, decodeJwt, decodeProtectedHeader, jwtVerify } from 'jose'; -import { readAccessRestrictionsFromConfig } from './helpers'; -import { AccessRestriptionsMap, TokenHandler } from './types'; -/** - * Handles `type: legacy` access. - * - * @internal - */ -export class LegacyTokenHandler implements TokenHandler { - #entries = new Array<{ - key: Uint8Array; - result: { - subject: string; - allAccessRestrictions?: AccessRestriptionsMap; - }; - }>(); +import { ExternalTokenHandler } from './types'; - add(config: Config) { - const allAccessRestrictions = readAccessRestrictionsFromConfig(config); - this.#doAdd( - config.getString('options.secret'), - config.getString('options.subject'), - allAccessRestrictions, - ); - } +export type LegacyConfigWrapper = { + legacy: boolean; + config: Config; +}; - // used only for the old backend.auth.keys array - addOld(config: Config) { - // This choice of subject is for compatibility reasons - this.#doAdd(config.getString('secret'), 'external:backstage-plugin'); - } +type LegacyTokenHandlerContext = { + key: Uint8Array; + + subject: string; +}; + +type LegacyTokenHandlerOverloaded = + ExternalTokenHandler & { + initialize(ctx: { + options: Config; + legacy: true; + }): LegacyTokenHandlerContext; + }; + +export const legacyTokenHandler: LegacyTokenHandlerOverloaded = { + type: 'legacy', + initialize(ctx: { + options: Config; + legacy?: true; + }): LegacyTokenHandlerContext { + const secret = ctx.options.getString('secret'); + const subject = ctx.legacy + ? 'external:backstage-plugin' + : ctx.options.getString('subject'); - #doAdd( - secret: string, - subject: string, - allAccessRestrictions?: AccessRestriptionsMap, - ) { if (!secret.match(/^\S+$/)) { throw new Error('Illegal secret, must be a valid base64 string'); } else if (!subject.match(/^\S+$/)) { throw new Error('Illegal subject, must be a set of non-space characters'); } - let key: Uint8Array; try { - key = base64url.decode(secret); + return { + key: base64url.decode(secret), + subject, + }; } catch { throw new Error('Illegal secret, must be a valid base64 string'); } + }, - if (this.#entries.some(e => e.key === key)) { - throw new Error( - 'Legacy externalAccess token was declared more than once', - ); - } - - this.#entries.push({ - key, - result: { - subject, - allAccessRestrictions, - }, - }); - } - - async verifyToken(token: string) { + async verifyToken(token: string, context: LegacyTokenHandlerContext) { // First do a duck typing check to see if it remotely looks like a legacy token try { // We do a fair amount of checking upfront here. Since we aren't certain @@ -102,19 +86,18 @@ export class LegacyTokenHandler implements TokenHandler { return undefined; } - for (const { key, result } of this.#entries) { - try { - await jwtVerify(token, key); - return result; - } catch (e) { - if (e.code !== 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED') { - throw e; - } - // Otherwise continue to try the next key + try { + await jwtVerify(token, context.key); + return { + subject: context.subject, + }; + } catch (error) { + if (error.code !== 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED') { + throw error; } } // None of the signing keys matched return undefined; - } -} + }, +}; diff --git a/packages/backend-defaults/src/entrypoints/auth/external/static.test.ts b/packages/backend-defaults/src/entrypoints/auth/external/static.test.ts index a5945daba9..10e78d2dbf 100644 --- a/packages/backend-defaults/src/entrypoints/auth/external/static.test.ts +++ b/packages/backend-defaults/src/entrypoints/auth/external/static.test.ts @@ -15,137 +15,146 @@ */ import { ConfigReader } from '@backstage/config'; -import { StaticTokenHandler } from './static'; +import { staticTokenHandler } from './static'; describe('StaticTokenHandler', () => { it('accepts any of the added list of tokens', async () => { - const handler = new StaticTokenHandler(); - handler.add( - new ConfigReader({ - options: { token: 'abcabcabc', subject: 'one' }, - accessRestrictions: [{ plugin: 'scaffolder' }], + const context1 = staticTokenHandler.initialize({ + options: new ConfigReader({ + token: 'abcabcabc', + subject: 'one', }), - ); - handler.add( - new ConfigReader({ - options: { token: 'defdefdef', subject: 'two' }, - accessRestrictions: [ - { plugin: 'catalog', permission: 'catalog.entity.read' }, - ], + }); + const context2 = staticTokenHandler.initialize({ + options: new ConfigReader({ + token: 'defdefdef', + subject: 'two', }), - ); - const accessRestrictionsOne = new Map(Object.entries({ scaffolder: {} })); - const accessRestrictionsTwo = new Map( - Object.entries({ - catalog: { - permissionNames: ['catalog.entity.read'], - }, - }), - ); + }); - await expect(handler.verifyToken('abcabcabc')).resolves.toEqual({ + await expect( + staticTokenHandler.verifyToken('abcabcabc', context1), + ).resolves.toEqual({ subject: 'one', - allAccessRestrictions: accessRestrictionsOne, }); - await expect(handler.verifyToken('defdefdef')).resolves.toEqual({ + await expect( + staticTokenHandler.verifyToken('defdefdef', context2), + ).resolves.toEqual({ subject: 'two', - allAccessRestrictions: accessRestrictionsTwo, }); - await expect(handler.verifyToken('ghighighi')).resolves.toBeUndefined(); + await expect( + staticTokenHandler.verifyToken('ghighighi', context1), + ).resolves.toBeUndefined(); }); it('gracefully handles no added tokens', async () => { - const handler = new StaticTokenHandler(); - await expect(handler.verifyToken('ghi')).resolves.toBeUndefined(); + await expect( + staticTokenHandler.verifyToken('ghi', {} as any), + ).resolves.toBeUndefined(); }); it('rejects bad config', () => { - const handler = new StaticTokenHandler(); - expect(() => - handler.add( - new ConfigReader({ options: { _missingtoken: true, subject: 'ok' } }), - ), + staticTokenHandler.initialize({ + options: new ConfigReader({ _missingtoken: true, subject: 'ok' }), + }), ).toThrowErrorMatchingInlineSnapshot( - `"Missing required config value at 'options.token' in 'mock-config'"`, + `"Missing required config value at 'token' in 'mock-config'"`, ); expect(() => - handler.add(new ConfigReader({ options: { token: '', subject: 'ok' } })), + staticTokenHandler.initialize({ + options: new ConfigReader({ token: '', subject: 'ok' }), + }), ).toThrowErrorMatchingInlineSnapshot( - `"Invalid type in config for key 'options.token' in 'mock-config', got empty-string, wanted string"`, + `"Invalid type in config for key 'token' in 'mock-config', got empty-string, wanted string"`, ); expect(() => - handler.add( - new ConfigReader({ options: { token: 'has spaces', subject: 'ok' } }), - ), - ).toThrowErrorMatchingInlineSnapshot( - `"Illegal token, must be a set of non-space characters"`, - ); - expect(() => - handler.add( - new ConfigReader({ - options: { - token: 'hasnewlinebutislongenough\n', - subject: 'ok', - }, + staticTokenHandler.initialize({ + options: new ConfigReader({ + token: 'has spaces', + subject: 'ok', }), - ), + }), ).toThrowErrorMatchingInlineSnapshot( `"Illegal token, must be a set of non-space characters"`, ); expect(() => - handler.add( - new ConfigReader({ options: { token: 'short', subject: 'ok' } }), - ), + staticTokenHandler.initialize({ + options: new ConfigReader({ + token: 'hasnewlinebutislongenough\n', + subject: 'ok', + }), + }), + ).toThrowErrorMatchingInlineSnapshot( + `"Illegal token, must be a set of non-space characters"`, + ); + expect(() => + staticTokenHandler.initialize({ + options: new ConfigReader({ + token: 'short', + subject: 'ok', + }), + }), ).toThrowErrorMatchingInlineSnapshot( `"Illegal token, must be at least 8 characters length"`, ); expect(() => - handler.add(new ConfigReader({ options: { token: 3, subject: 'ok' } })), + staticTokenHandler.initialize({ + options: new ConfigReader({ token: 3, subject: 'ok' }), + }), ).toThrowErrorMatchingInlineSnapshot( - `"Invalid type in config for key 'options.token' in 'mock-config', got number, wanted string"`, + `"Invalid type in config for key 'token' in 'mock-config', got number, wanted string"`, ); expect(() => - handler.add( - new ConfigReader({ - options: { token: 'validtoken', _missingsubject: true }, + staticTokenHandler.initialize({ + options: new ConfigReader({ + token: 'validtoken', + _missingsubject: true, }), - ), + }), ).toThrowErrorMatchingInlineSnapshot( - `"Missing required config value at 'options.subject' in 'mock-config'"`, + `"Missing required config value at 'subject' in 'mock-config'"`, ); expect(() => - handler.add( - new ConfigReader({ options: { token: 'validtoken', subject: '' } }), - ), + staticTokenHandler.initialize({ + options: new ConfigReader({ + token: 'validtoken', + subject: '', + }), + }), ).toThrowErrorMatchingInlineSnapshot( - `"Invalid type in config for key 'options.subject' in 'mock-config', got empty-string, wanted string"`, + `"Invalid type in config for key 'subject' in 'mock-config', got empty-string, wanted string"`, ); expect(() => - handler.add( - new ConfigReader({ - options: { token: 'validtoken', subject: 'has spaces' }, + staticTokenHandler.initialize({ + options: new ConfigReader({ + token: 'validtoken', + subject: 'has spaces', }), - ), + }), ).toThrowErrorMatchingInlineSnapshot( `"Illegal subject, must be a set of non-space characters"`, ); expect(() => - handler.add( - new ConfigReader({ - options: { token: 'validtoken', subject: 'hasnewline\n' }, + staticTokenHandler.initialize({ + options: new ConfigReader({ + token: 'validtoken', + subject: 'hasnewline\n', }), - ), + }), ).toThrowErrorMatchingInlineSnapshot( `"Illegal subject, must be a set of non-space characters"`, ); expect(() => - handler.add( - new ConfigReader({ options: { token: 'validtoken', subject: 3 } }), - ), + staticTokenHandler.initialize({ + options: new ConfigReader({ + token: 'validtoken', + subject: 3, + }), + }), ).toThrowErrorMatchingInlineSnapshot( - `"Invalid type in config for key 'options.subject' in 'mock-config', got number, wanted string"`, + `"Invalid type in config for key 'subject' in 'mock-config', got number, wanted string"`, ); }); }); diff --git a/packages/backend-defaults/src/entrypoints/auth/external/static.ts b/packages/backend-defaults/src/entrypoints/auth/external/static.ts index 1e89d8c6a1..502150d5a8 100644 --- a/packages/backend-defaults/src/entrypoints/auth/external/static.ts +++ b/packages/backend-defaults/src/entrypoints/auth/external/static.ts @@ -15,29 +15,18 @@ */ import { Config } from '@backstage/config'; -import { readAccessRestrictionsFromConfig } from './helpers'; -import { AccessRestriptionsMap, TokenHandler } from './types'; +import { createExternalTokenHandler } from './helpers'; const MIN_TOKEN_LENGTH = 8; -/** - * Handles `type: static` access. - * - * @internal - */ -export class StaticTokenHandler implements TokenHandler { - #entries = new Map< - string, - { - subject: string; - allAccessRestrictions?: AccessRestriptionsMap; - } - >(); - - add(config: Config) { - const token = config.getString('options.token'); - const subject = config.getString('options.subject'); - const allAccessRestrictions = readAccessRestrictionsFromConfig(config); +export const staticTokenHandler = createExternalTokenHandler<{ + token: string; + subject: string; +}>({ + type: 'static', + initialize(ctx: { options: Config }): { token: string; subject: string } { + const token = ctx.options.getString('token'); + const subject = ctx.options.getString('subject'); if (!token.match(/^\S+$/)) { throw new Error('Illegal token, must be a set of non-space characters'); @@ -47,16 +36,17 @@ export class StaticTokenHandler implements TokenHandler { ); } else if (!subject.match(/^\S+$/)) { throw new Error('Illegal subject, must be a set of non-space characters'); - } else if (this.#entries.has(token)) { - throw new Error( - 'Static externalAccess token was declared more than once', - ); } - this.#entries.set(token, { subject, allAccessRestrictions }); - } - - async verifyToken(token: string) { - return this.#entries.get(token); - } -} + return { token, subject }; + }, + async verifyToken( + token: string, + context: { token: string; subject: string }, + ) { + if (token === context.token) { + return { subject: context.subject }; + } + return undefined; + }, +}); diff --git a/packages/backend-defaults/src/entrypoints/auth/external/types.ts b/packages/backend-defaults/src/entrypoints/auth/external/types.ts index 6a1fd084ed..01d5a26149 100644 --- a/packages/backend-defaults/src/entrypoints/auth/external/types.ts +++ b/packages/backend-defaults/src/entrypoints/auth/external/types.ts @@ -15,20 +15,23 @@ */ import { BackstagePrincipalAccessRestrictions } from '@backstage/backend-plugin-api'; -import { Config } from '@backstage/config'; +import type { Config } from '@backstage/config'; -export type AccessRestriptionsMap = Map< +export type AccessRestrictionsMap = Map< string, // plugin ID BackstagePrincipalAccessRestrictions >; -export interface TokenHandler { - add(options: Config): void; - verifyToken(token: string): Promise< - | { - subject: string; - allAccessRestrictions?: AccessRestriptionsMap; - } - | undefined - >; +/** + * @public + * This interface is used to handle external tokens. + * It is used by the auth service to verify tokens and extract the subject. + */ +export interface ExternalTokenHandler { + type: string; + initialize(ctx: { options: Config }): TContext; + verifyToken( + token: string, + ctx: TContext, + ): Promise<{ subject: string } | undefined>; } diff --git a/packages/backend-defaults/src/entrypoints/auth/index.ts b/packages/backend-defaults/src/entrypoints/auth/index.ts index e48926f0ce..69d41b0310 100644 --- a/packages/backend-defaults/src/entrypoints/auth/index.ts +++ b/packages/backend-defaults/src/entrypoints/auth/index.ts @@ -19,4 +19,10 @@ export { pluginTokenHandlerDecoratorServiceRef, } from './authServiceFactory'; +export { createExternalTokenHandler } from './external/helpers'; + +export { externalTokenHandlersServiceRef } from './external/ExternalAuthTokenHandler'; + +export type { ExternalTokenHandler } from './external/types'; + export type { PluginTokenHandler } from './plugin/PluginTokenHandler'; diff --git a/packages/backend-defaults/src/entrypoints/auth/plugin/PluginTokenHandler.ts b/packages/backend-defaults/src/entrypoints/auth/plugin/PluginTokenHandler.ts index 9aec52bc08..b692cd2b87 100644 --- a/packages/backend-defaults/src/entrypoints/auth/plugin/PluginTokenHandler.ts +++ b/packages/backend-defaults/src/entrypoints/auth/plugin/PluginTokenHandler.ts @@ -46,7 +46,7 @@ type Options = { /** * @public - * Issues and verifies {@link https://backstage.iceio/docs/auth/service-to-service-auth | service-to-service tokens}. + * Issues and verifies {@link https://backstage.io/docs/auth/service-to-service-auth | service-to-service tokens}. */ export interface PluginTokenHandler { verifyToken( diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.test.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.test.ts index 0f2b6463e4..8f8d1b7656 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.test.ts +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.test.ts @@ -323,4 +323,65 @@ describe('rootHttpRouterServiceFactory', () => { }), ).resolves.toBeUndefined(); }); + + it('should start successfully with server configuration options', async () => { + const { app } = await createExpressApp( + mockServices.rootConfig.factory({ + data: { + backend: { + listen: { port: 0 }, + server: { + headersTimeout: 30000, + requestTimeout: '45s', + keepAliveTimeout: 'PT1M', + timeout: { seconds: 120 }, + maxHeadersCount: 2000, + maxRequestsPerSocket: 100, + }, + }, + }, + }), + ); + + // Verify the server starts and responds to health checks + await request(app) + .get('/.backstage/health/v1/liveness') + .expect(200, { status: 'ok' }); + }); + + it('should start successfully with partial server configuration', async () => { + const { app } = await createExpressApp( + mockServices.rootConfig.factory({ + data: { + backend: { + listen: { port: 0 }, + server: { + headersTimeout: 60000, + maxHeadersCount: 1500, + }, + }, + }, + }), + ); + + await request(app) + .get('/.backstage/health/v1/liveness') + .expect(200, { status: 'ok' }); + }); + + it('should start successfully with no server configuration', async () => { + const { app } = await createExpressApp( + mockServices.rootConfig.factory({ + data: { + backend: { + listen: { port: 0 }, + }, + }, + }), + ); + + await request(app) + .get('/.backstage/health/v1/liveness') + .expect(200, { status: 'ok' }); + }); }); diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.ts index 3b50515742..b0cc9d160e 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.ts +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.ts @@ -117,6 +117,75 @@ const rootHttpRouterServiceFactoryWithOptions = ( if (trustProxy !== undefined) { app.set('trust proxy', trustProxy); } + + // Apply server-level HTTP options from config + const backendConfig = config.getOptionalConfig('backend'); + const serverConfig = backendConfig?.getOptionalConfig('server'); + + if (serverConfig) { + // Helper function to read duration values (supporting number, string, or HumanDuration) + const readDurationValue = (key: string): number | undefined => { + if (!serverConfig.has(key)) { + return undefined; + } + + const value = serverConfig.getOptional(key); + if (typeof value === 'number') { + return value; + } + + // If it's not a number, try to read it as a duration + try { + const duration = readDurationFromConfig(serverConfig, { key }); + return durationToMilliseconds(duration); + } catch (error) { + // Log warning for parsing failures + logger.warn( + `Failed to parse backend.server.${key} as duration: ${error}. ` + + `Expected a number (milliseconds), duration string (e.g., '30s'), ` + + `ISO duration (e.g., 'PT30S'), or duration object (e.g., {seconds: 30}). ` + + `Falling back to number parsing.`, + ); + return undefined; + } + }; + + // Apply timeout settings + const headersTimeout = readDurationValue('headersTimeout'); + if (headersTimeout !== undefined) { + server.headersTimeout = headersTimeout; + } + + const requestTimeout = readDurationValue('requestTimeout'); + if (requestTimeout !== undefined) { + server.requestTimeout = requestTimeout; + } + + const keepAliveTimeout = readDurationValue('keepAliveTimeout'); + if (keepAliveTimeout !== undefined) { + server.keepAliveTimeout = keepAliveTimeout; + } + + const timeout = readDurationValue('timeout'); + if (timeout !== undefined) { + server.timeout = timeout; + } + + // Apply numeric settings + const maxHeadersCount = + serverConfig.getOptionalNumber('maxHeadersCount'); + if (maxHeadersCount !== undefined) { + server.maxHeadersCount = maxHeadersCount; + } + + const maxRequestsPerSocket = serverConfig.getOptionalNumber( + 'maxRequestsPerSocket', + ); + if (maxRequestsPerSocket !== undefined) { + server.maxRequestsPerSocket = maxRequestsPerSocket; + } + } + app.use(middleware.helmet()); app.use(middleware.cors()); app.use(middleware.compression()); diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/AzureBlobStorageUrlReader.test.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/AzureBlobStorageUrlReader.test.ts new file mode 100644 index 0000000000..93f0d81441 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/AzureBlobStorageUrlReader.test.ts @@ -0,0 +1,359 @@ +/* + * 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 * as AzureStorage from '@azure/storage-blob'; +import { ConfigReader } from '@backstage/config'; +import { JsonObject } from '@backstage/types'; +import { DefaultReadTreeResponseFactory } from './tree'; +import { + AzureBlobStorageUrlReader, + parseUrl, +} from './AzureBlobStorageUrlReader'; +import { + DefaultAzureCredentialsManager, + ScmIntegrations, +} from '@backstage/integration'; +import { UrlReaderPredicateTuple } from './types'; +import { mockServices } from '@backstage/backend-test-utils'; +import { Readable } from 'stream'; + +// Mock Azure Blob Storage SDK +const mockBlobDownload = jest.fn(); +const mockGetBlobClient = jest.fn(() => ({ + download: mockBlobDownload, +})); +const mockListBlobsFlat = jest.fn(); + +class MockContainerClient { + getBlobClient = mockGetBlobClient; + listBlobsFlat = mockListBlobsFlat; +} + +class MockBlobServiceClient { + getContainerClient = jest.fn(() => new MockContainerClient()); +} + +jest + .spyOn(AzureStorage, 'BlobServiceClient') + .mockReturnValue(new MockBlobServiceClient() as any); +jest + .spyOn(AzureStorage, 'StorageSharedKeyCredential') + .mockReturnValue({} as any); + +const treeResponseFactory = DefaultReadTreeResponseFactory.create({ + config: new ConfigReader({}), +}); + +describe('parseUrl', () => { + it('parses Azure Blob Storage URLs correctly', () => { + expect( + parseUrl( + 'https://test-account.blob.core.windows.net/mycontainer/path/to/file.yaml', + ), + ).toEqual({ + path: 'path/to/file.yaml', + container: 'mycontainer', + }); + + expect( + parseUrl( + 'https://test-account.blob.core.windows.net/mycontainer/file.yaml', + ), + ).toEqual({ + path: 'file.yaml', + container: 'mycontainer', + }); + }); + + it('handles URLs with no path correctly', () => { + expect( + parseUrl('https://test-account.blob.core.windows.net/mycontainer/'), + ).toEqual({ + path: '', + container: 'mycontainer', + }); + }); + + it('throws error for invalid URLs', () => { + expect(() => + parseUrl('https://test-account.blob.core.windows.net/'), + ).toThrow('Invalid Azure Blob Storage URL format'); + }); +}); + +describe('AzureBlobStorageUrlReader', () => { + const createReader = (config: JsonObject): UrlReaderPredicateTuple[] => { + return AzureBlobStorageUrlReader.factory({ + config: new ConfigReader(config), + logger: mockServices.logger.mock(), + treeResponseFactory, + }); + }; + + it('creates a reader with minimal config', () => { + const entries = createReader({ + integrations: { + azureBlobStorage: [ + { + accountName: 'test-account', + }, + ], + }, + }); + + expect(entries).toHaveLength(1); + }); + + describe('predicates', () => { + const readers = createReader({ + integrations: { + azureBlobStorage: [ + { + accountName: 'test-account', + }, + ], + }, + }); + const predicate = readers[0].predicate; + + it('returns true for the correct azure blob storage host', () => { + expect( + predicate(new URL('https://test-account.blob.core.windows.net')), + ).toBe(true); + }); + + it('returns false for an incorrect host', () => { + expect( + predicate(new URL('https://wrongaccount.blob.core.windows.net')), + ).toBe(false); + }); + }); + + describe('toString', () => { + it('returns a string representation with account name and auth status', () => { + const config = new ConfigReader({ + integrations: { + azureBlobStorage: [ + { + accountName: 'test-account', + accountKey: 'test-key', + }, + ], + }, + }); + + const integrations = ScmIntegrations.fromConfig(config); + const credsManager = + DefaultAzureCredentialsManager.fromIntegrations(integrations); + const reader = new AzureBlobStorageUrlReader( + credsManager, + integrations.azureBlobStorage.list()[0], + { treeResponseFactory }, + ); + + expect(reader.toString()).toBe( + 'azureBlobStorage{accountName=test-account,authed=true}', + ); + }); + + it('shows authed=false when no account key provided', () => { + const config = new ConfigReader({ + integrations: { + azureBlobStorage: [ + { + accountName: 'test-account', + }, + ], + }, + }); + + const integrations = ScmIntegrations.fromConfig(config); + const credsManager = + DefaultAzureCredentialsManager.fromIntegrations(integrations); + const reader = new AzureBlobStorageUrlReader( + credsManager, + integrations.azureBlobStorage.list()[0], + { treeResponseFactory }, + ); + + expect(reader.toString()).toBe( + 'azureBlobStorage{accountName=test-account,authed=false}', + ); + }); + }); + + describe('readUrl', () => { + const [{ reader }] = createReader({ + integrations: { + azureBlobStorage: [ + { + accountName: 'test-account', + accountKey: 'test-key', + }, + ], + }, + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('returns contents of a blob via buffer', async () => { + mockBlobDownload.mockResolvedValue({ + readableStreamBody: Readable.from( + Buffer.from('site_name: Test Azure Blob'), + ), + etag: '"etag"', + lastModified: new Date('2025-01-01T00:00:00Z'), + }); + + const { buffer, etag, lastModifiedAt } = await reader.readUrl( + 'https://test-account.blob.core.windows.net/test-container/test-file.yaml', + ); + + expect(etag).toBe('"etag"'); + expect(lastModifiedAt).toEqual(new Date('2025-01-01T00:00:00Z')); + const response = await buffer(); + expect(response.toString()).toBe('site_name: Test Azure Blob'); + }); + + it('handles Azure SDK errors', async () => { + mockBlobDownload.mockRejectedValue(new Error('Blob not found')); + + await expect( + reader.readUrl( + 'https://test-account.blob.core.windows.net/test-container/nonexistent.yaml', + ), + ).rejects.toThrow('Could not retrieve file from Azure Blob Storage'); + }); + }); + + describe('readTree', () => { + const [{ reader }] = createReader({ + integrations: { + azureBlobStorage: [ + { + accountName: 'test-account', + accountKey: 'test-key', + }, + ], + }, + }); + + beforeEach(() => { + jest.clearAllMocks(); + const mockBlobs = [ + { + name: 'prefix/file1.yaml', + properties: { + lastModified: new Date('2025-01-01T00:00:00Z'), + }, + }, + { + name: 'prefix/subdir/file2.yaml', + properties: { + lastModified: new Date('2024-01-01T00:00:00Z'), + }, + }, + ]; + + mockBlobDownload.mockResolvedValue({ + readableStreamBody: Readable.from( + Buffer.from('site_name: Test Azure Blob'), + ), + }); + + mockListBlobsFlat.mockReturnValue({ + [Symbol.asyncIterator]: async function* generateBlobs() { + for (const blob of mockBlobs) { + yield blob; + } + }, + }); + }); + + it('returns contents of blobs in a container', async () => { + const response = await reader.readTree( + 'https://test-account.blob.core.windows.net/test-container/prefix', + ); + const files = await response.files(); + + expect(files).toHaveLength(2); + const file1Content = await files[0].content(); + expect(file1Content.toString()).toBe('site_name: Test Azure Blob'); + }); + }); + + describe('search', () => { + const [{ reader }] = createReader({ + integrations: { + azureBlobStorage: [ + { + accountName: 'test-account', + accountKey: 'test-key', + }, + ], + }, + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should return a file when given an exact valid url', async () => { + mockBlobDownload.mockResolvedValue({ + readableStreamBody: Readable.from( + Buffer.from('site_name: Test Azure Blob'), + ), + etag: '"etag"', + lastModified: new Date('2025-01-01T00:00:00Z'), + }); + + const data = await reader.search( + 'https://test-account.blob.core.windows.net/test-container/test-file.yaml', + ); + + expect(data.etag).toBe('"etag"'); + expect(data.files.length).toBe(1); + expect(data.files[0].url).toBe( + 'https://test-account.blob.core.windows.net/test-container/test-file.yaml', + ); + expect((await data.files[0].content()).toString()).toEqual( + 'site_name: Test Azure Blob', + ); + }); + + it('should handle Azure SDK errors from readUrl', async () => { + mockBlobDownload.mockRejectedValue(new Error('Blob not found')); + + await expect( + reader.search( + 'https://test-account.blob.core.windows.net/test-container/missing.yaml', + ), + ).rejects.toThrow('Could not retrieve file from Azure Blob Storage'); + }); + + it('throws if given URL with wildcard', async () => { + await expect( + reader.search( + 'https://test-account.blob.core.windows.net/test-container/test-*.yaml', + ), + ).rejects.toThrow( + 'Glob search pattern not implemented for AzureBlobStorageUrlReader', + ); + }); + }); +}); diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/AzureBlobStorageUrlReader.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/AzureBlobStorageUrlReader.ts index e04e2c5b47..1953beabe9 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/AzureBlobStorageUrlReader.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/AzureBlobStorageUrlReader.ts @@ -21,7 +21,11 @@ import { StorageSharedKeyCredential, } from '@azure/storage-blob'; import { ReaderFactory, ReadTreeResponseFactory } from './types'; -import { ForwardedError, NotModifiedError } from '@backstage/errors'; +import { + assertError, + ForwardedError, + NotModifiedError, +} from '@backstage/errors'; import { Readable } from 'stream'; import { relative } from 'path/posix'; import { ReadUrlResponseFactory } from './ReadUrlResponseFactory'; @@ -37,6 +41,7 @@ import { UrlReaderServiceReadTreeResponse, UrlReaderServiceReadUrlOptions, UrlReaderServiceReadUrlResponse, + UrlReaderServiceSearchOptions, UrlReaderServiceSearchResponse, } from '@backstage/backend-plugin-api'; @@ -190,13 +195,13 @@ export class AzureBlobStorageUrlReader implements UrlReaderService { const { path, container } = parseUrl(url); const containerClient = await this.createContainerClient(container); - const blobs = containerClient.listBlobsFlat({ prefix: path }); const responses = []; for await (const blob of blobs) { const blobClient = containerClient.getBlobClient(blob.name); + const downloadBlockBlobResponse = await blobClient.download( undefined, undefined, @@ -221,8 +226,35 @@ export class AzureBlobStorageUrlReader implements UrlReaderService { } } - async search(): Promise { - throw new Error('AzureBlobStorageUrlReader does not implement search'); + async search( + url: string, + options?: UrlReaderServiceSearchOptions, + ): Promise { + const { path } = parseUrl(url); + + if (path.match(/[*?]/)) { + throw new Error( + 'Glob search pattern not implemented for AzureBlobStorageUrlReader', + ); + } + + try { + const data = await this.readUrl(url, options); + + return { + files: [ + { + url: url, + content: data.buffer, + lastModifiedAt: data.lastModifiedAt, + }, + ], + etag: data.etag ?? '', + }; + } catch (error) { + assertError(error); + throw error; + } } toString() { diff --git a/packages/backend-dynamic-feature-service/CHANGELOG.md b/packages/backend-dynamic-feature-service/CHANGELOG.md index e3f857785d..a15b315a62 100644 --- a/packages/backend-dynamic-feature-service/CHANGELOG.md +++ b/packages/backend-dynamic-feature-service/CHANGELOG.md @@ -1,5 +1,52 @@ # @backstage/backend-dynamic-feature-service +## 0.7.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.4-next.0 + - @backstage/config@1.3.4-next.0 + - @backstage/backend-defaults@0.13.0-next.1 + - @backstage/plugin-app-node@0.1.38-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + - @backstage/plugin-catalog-backend@3.1.1-next.1 + - @backstage/plugin-events-backend@0.5.7-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-permission-node@0.10.5-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.1 + - @backstage/plugin-search-backend-node@1.3.16-next.0 + - @backstage/backend-openapi-utils@0.6.2-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + - @backstage/cli-node@0.2.14 + - @backstage/plugin-search-common@1.2.20-next.0 + +## 0.7.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.1.1-next.0 + - @backstage/backend-defaults@0.13.0-next.0 + - @backstage/plugin-scaffolder-node@0.12.0-next.0 + - @backstage/plugin-auth-node@0.6.7 + - @backstage/plugin-events-backend@0.5.6 + - @backstage/plugin-permission-node@0.10.4 + - @backstage/plugin-search-backend-node@1.3.15 + - @backstage/backend-openapi-utils@0.6.1 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/cli-common@0.1.15 + - @backstage/cli-node@0.2.14 + - @backstage/config@1.3.3 + - @backstage/config-loader@1.10.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-app-node@0.1.37 + - @backstage/plugin-events-node@0.4.15 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-search-common@1.2.19 + ## 0.7.4 ### Patch Changes diff --git a/packages/backend-dynamic-feature-service/package.json b/packages/backend-dynamic-feature-service/package.json index 04577cbf43..78e881a6bc 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.4", + "version": "0.7.5-next.1", "description": "Backstage dynamic feature service", "backstage": { "role": "node-library" diff --git a/packages/backend-openapi-utils/CHANGELOG.md b/packages/backend-openapi-utils/CHANGELOG.md index 87bea0405e..61815004c3 100644 --- a/packages/backend-openapi-utils/CHANGELOG.md +++ b/packages/backend-openapi-utils/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/backend-openapi-utils +## 0.6.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + ## 0.6.1 ### Patch Changes diff --git a/packages/backend-openapi-utils/package.json b/packages/backend-openapi-utils/package.json index 31d7aaf486..48211fa851 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.1", + "version": "0.6.2-next.0", "description": "OpenAPI typescript support.", "backstage": { "role": "node-library" diff --git a/packages/backend-plugin-api/CHANGELOG.md b/packages/backend-plugin-api/CHANGELOG.md index 914d45f68b..44931c21e1 100644 --- a/packages/backend-plugin-api/CHANGELOG.md +++ b/packages/backend-plugin-api/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/backend-plugin-api +## 1.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-permission-node@0.10.5-next.0 + ## 1.4.3 ### Patch Changes diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index baebdcd506..fcdbd15107 100644 --- a/packages/backend-plugin-api/package.json +++ b/packages/backend-plugin-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-plugin-api", - "version": "1.4.3", + "version": "1.4.4-next.0", "description": "Core API used by Backstage backend plugins", "backstage": { "role": "node-library" diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md index cc82ef5593..536d595d34 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/backend-test-utils +## 1.9.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-defaults@0.13.0-next.1 + - @backstage/backend-app-api@1.2.8-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + +## 1.9.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.0-next.0 + - @backstage/backend-app-api@1.2.7 + - @backstage/plugin-auth-node@0.6.7 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.15 + - @backstage/plugin-permission-common@0.9.1 + ## 1.9.0 ### Minor Changes diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index 868c98a8f2..3ee214b345 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.9.0", + "version": "1.9.1-next.1", "description": "Test helpers library for Backstage backends", "backstage": { "role": "node-library" diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index 398465966e..f8c601b473 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,86 @@ # example-backend +## 0.0.43-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.0-next.1 + - @backstage/plugin-app-backend@0.5.7-next.0 + - @backstage/plugin-devtools-backend@0.5.10-next.1 + - @backstage/plugin-proxy-backend@0.6.7-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-backend@0.25.5-next.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.13-next.0 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.1-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + - @backstage/plugin-catalog-backend@3.1.1-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.7-next.0 + - @backstage/plugin-events-backend@0.5.7-next.0 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.5-next.0 + - @backstage/plugin-kubernetes-backend@0.20.3-next.1 + - @backstage/plugin-notifications-backend@0.5.11-next.0 + - @backstage/plugin-permission-backend@0.7.5-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-permission-node@0.10.5-next.0 + - @backstage/plugin-scaffolder-backend@3.0.0-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.9.1-next.1 + - @backstage/plugin-search-backend@2.0.7-next.1 + - @backstage/plugin-search-backend-module-catalog@0.3.9-next.0 + - @backstage/plugin-search-backend-module-explore@0.3.8-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.4.7-next.1 + - @backstage/plugin-search-backend-node@1.3.16-next.0 + - @backstage/plugin-signals-backend@0.3.9-next.0 + - @backstage/plugin-techdocs-backend@2.1.1-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.3.8-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.2.15-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.13-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.5-next.0 + - @backstage/plugin-mcp-actions-backend@0.1.4-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.13-next.0 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.15-next.1 + +## 0.0.43-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.1.1-next.0 + - @backstage/backend-defaults@0.13.0-next.0 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.15-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.9.1-next.0 + - @backstage/plugin-scaffolder-backend@3.0.0-next.0 + - @backstage/plugin-app-backend@0.5.6 + - @backstage/plugin-auth-backend@0.25.4 + - @backstage/plugin-auth-backend-module-github-provider@0.3.7 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.0 + - @backstage/plugin-auth-node@0.6.7 + - @backstage/plugin-devtools-backend@0.5.10-next.0 + - @backstage/plugin-events-backend@0.5.6 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.4 + - @backstage/plugin-kubernetes-backend@0.20.3-next.0 + - @backstage/plugin-mcp-actions-backend@0.1.4-next.0 + - @backstage/plugin-notifications-backend@0.5.10 + - @backstage/plugin-permission-backend@0.7.4 + - @backstage/plugin-permission-node@0.10.4 + - @backstage/plugin-proxy-backend@0.6.6 + - @backstage/plugin-search-backend@2.0.7-next.0 + - @backstage/plugin-search-backend-node@1.3.15 + - @backstage/plugin-signals-backend@0.3.8 + - @backstage/plugin-techdocs-backend@2.1.1-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.2.15-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-model@1.7.5 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.12 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.6 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.13-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.4 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.12 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-search-backend-module-catalog@0.3.8 + - @backstage/plugin-search-backend-module-explore@0.3.7 + - @backstage/plugin-search-backend-module-techdocs@0.4.7-next.0 + ## 0.0.42 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index 8bca4f3b22..c698a424a5 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.0.42", + "version": "0.0.43-next.1", "backstage": { "role": "backend" }, diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 7947c15cbb..7382ef147b 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/cli +## 0.34.4-next.2 + +### Patch Changes + +- ab96bb7: Added a new `--entrypoint` option to the `package start` command, which allows you to specify a custom entry directory/file for development applications. This is particularly useful when maintaining separate dev apps for different versions of your plugin (e.g., stable and alpha). + + **Example usage:** + + Consider the following plugin dev folder structure: + + ``` + dev/ + index.tsx + alpha/ + index.ts + ``` + + - The default `yarn package start` command uses the `dev/` folder as the entry point and executes `dev/index.tsx` file; + - Running `yarn package start --entrypoint dev/alpha` will instead use `dev/alpha/` as the entry point and execute `dev/alpha/index.ts` file. + +## 0.34.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.4-next.0 + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/cli-node@0.2.14 + - @backstage/release-manifests@0.0.13 + +## 0.34.4-next.0 + +### Patch Changes + +- 6ebc1ea: Fixed module federation config by only setting `import: false` on shared libraries for remote. +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/cli-common@0.1.15 + - @backstage/cli-node@0.2.14 + - @backstage/config@1.3.3 + - @backstage/config-loader@1.10.3 + - @backstage/errors@1.2.7 + - @backstage/eslint-plugin@0.1.11 + - @backstage/release-manifests@0.0.13 + - @backstage/types@1.2.2 + ## 0.34.2 ### Patch Changes diff --git a/packages/cli/cli-report.md b/packages/cli/cli-report.md index e2f5bb9cc6..a8df5242ad 100644 --- a/packages/cli/cli-report.md +++ b/packages/cli/cli-report.md @@ -312,6 +312,7 @@ Usage: program [options] Options: --check --config + --entrypoint --inspect [host] --inspect-brk [host] --link diff --git a/packages/cli/package.json b/packages/cli/package.json index f50a5601eb..770aac72a6 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/cli", - "version": "0.34.2", + "version": "0.34.4-next.2", "description": "CLI for developing Backstage plugins and apps", "backstage": { "role": "cli" diff --git a/packages/cli/src/lib/version.test.ts b/packages/cli/src/lib/version.test.ts index 744874d7e9..4cb7591faf 100644 --- a/packages/cli/src/lib/version.test.ts +++ b/packages/cli/src/lib/version.test.ts @@ -79,4 +79,110 @@ describe('createPackageVersionProvider', () => { `^${corePluginApiPkg.version}`, ); }); + + describe('with backstage protocol options', () => { + it('should return backstage:^ for @backstage packages when preferBackstageProtocol is true', async () => { + mockDir.setContent({ + 'yarn.lock': `${HEADER} +"@backstage/core-plugin-api@^1.0.0": + version "1.0.0" +`, + }); + + const lockfilePath = mockDir.resolve('yarn.lock'); + const lockfile = await Lockfile.load(lockfilePath); + const provider = createPackageVersionProvider(lockfile, { + preferBackstageProtocol: true, + }); + + expect(provider('@backstage/core-plugin-api')).toBe('backstage:^'); + expect(provider('@backstage/cli')).toBe('backstage:^'); + }); + + it('should not return backstage:^ for non-@backstage packages even when preferBackstageProtocol is true', async () => { + mockDir.setContent({ + 'yarn.lock': `${HEADER} +"react@^18.0.0": + version "18.0.0" + +"@backstage/core-plugin-api@^1.0.0": + version "1.0.0" + +"@internal/library@workspace:packages/internal": + version "0.0.0-use.local" +`, + }); + + const lockfilePath = mockDir.resolve('yarn.lock'); + const lockfile = await Lockfile.load(lockfilePath); + const provider = createPackageVersionProvider(lockfile, { + preferBackstageProtocol: true, + }); + + expect(provider('react', '18.0.0')).toBe('^18.0.0'); + expect(provider('@backstage/core-plugin-api')).toBe('backstage:^'); + expect(provider('@internal/library')).toBe('workspace:^'); + }); + + it('should prefer workspace ranges over backstage protocol', async () => { + mockDir.setContent({ + 'yarn.lock': `${HEADER} +"react@workspace:packages/internal": + version "0.0.0-use.local" + +"@backstage/core-plugin-api@workspace:packages/internal": + version "0.0.0-use.local" + +"@internal/library@workspace:packages/internal": + version "0.0.0-use.local" +`, + }); + + const lockfilePath = mockDir.resolve('yarn.lock'); + const lockfile = await Lockfile.load(lockfilePath); + const provider = createPackageVersionProvider(lockfile, { + preferBackstageProtocol: true, + }); + + expect(provider('react')).toBe('workspace:^'); + expect(provider('@backstage/core-plugin-api')).toBe('workspace:^'); + expect(provider('@internal/library')).toBe('workspace:^'); + }); + + it('should not use backstage protocol when preferBackstageProtocol is false', async () => { + mockDir.setContent({ + 'yarn.lock': `${HEADER} +"@backstage/core-plugin-api@^1.0.0": + version "1.0.0" +`, + }); + + const lockfilePath = mockDir.resolve('yarn.lock'); + const lockfile = await Lockfile.load(lockfilePath); + const provider = createPackageVersionProvider(lockfile, { + preferBackstageProtocol: false, + }); + + expect(provider('@backstage/core-plugin-api')).toBe( + `^${corePluginApiPkg.version}`, + ); + }); + + it('should not use backstage protocol when options are not provided', async () => { + mockDir.setContent({ + 'yarn.lock': `${HEADER} +"@backstage/core-plugin-api@^1.0.0": + version "1.0.0" +`, + }); + + const lockfilePath = mockDir.resolve('yarn.lock'); + const lockfile = await Lockfile.load(lockfilePath); + const provider = createPackageVersionProvider(lockfile); + + expect(provider('@backstage/core-plugin-api')).toBe( + `^${corePluginApiPkg.version}`, + ); + }); + }); }); diff --git a/packages/cli/src/lib/version.ts b/packages/cli/src/lib/version.ts index 5a2a8c7838..a7a7ca1bdb 100644 --- a/packages/cli/src/lib/version.ts +++ b/packages/cli/src/lib/version.ts @@ -89,23 +89,35 @@ export function findVersion() { export const version = findVersion(); export const isDev = fs.pathExistsSync(paths.resolveOwn('src')); -export function createPackageVersionProvider(lockfile?: Lockfile) { +export function createPackageVersionProvider( + lockfile?: Lockfile, + options?: { + preferBackstageProtocol?: boolean; + }, +) { return (name: string, versionHint?: string): string => { const packageVersion = packageVersions[name]; + + // 1) workspace precedence (existing logic) - check this first + const lockfileEntries = lockfile?.get(name); + const lockfileEntry = lockfileEntries?.find(entry => + entry.range.startsWith('workspace:'), + ); + if (lockfileEntry) { + return 'workspace:^'; + } + + // 2) backstage:^ when plugin is present and allowed + if (options?.preferBackstageProtocol && name.startsWith('@backstage/')) { + return 'backstage:^'; + } + + // 3) fallback to current npm resolution const targetVersion = versionHint || packageVersion; if (!targetVersion) { throw new Error(`No version available for package ${name}`); } - const lockfileEntries = lockfile?.get(name); - - for (const specifier of ['^', '~', '*']) { - const range = `workspace:${specifier}`; - if (lockfileEntries?.some(entry => entry.range === range)) { - return range; - } - } - const validRanges = lockfileEntries?.filter(entry => semver.satisfies(targetVersion, entry.range), ); diff --git a/packages/cli/src/lib/yarnPlugin.test.ts b/packages/cli/src/lib/yarnPlugin.test.ts new file mode 100644 index 0000000000..d5c865e1c4 --- /dev/null +++ b/packages/cli/src/lib/yarnPlugin.test.ts @@ -0,0 +1,131 @@ +/* + * 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 { createMockDirectory } from '@backstage/backend-test-utils'; +import { getHasYarnPlugin } from './yarnPlugin'; + +const mockDir = createMockDirectory(); + +jest.mock('./paths', () => ({ + paths: { + resolveTargetRoot(filename: string) { + return mockDir.resolve(filename); + }, + }, +})); + +describe('getHasYarnPlugin', () => { + beforeEach(() => { + mockDir.clear(); + }); + + it('should return false when .yarnrc.yml does not exist', async () => { + mockDir.setContent({}); + + const result = await getHasYarnPlugin(); + expect(result).toBe(false); + }); + + it('should return false when .yarnrc.yml is empty', async () => { + mockDir.setContent({ + '.yarnrc.yml': '', + }); + + const result = await getHasYarnPlugin(); + expect(result).toBe(false); + }); + + it('should return false when plugins array is empty', async () => { + mockDir.setContent({ + '.yarnrc.yml': 'plugins: []', + }); + + const result = await getHasYarnPlugin(); + expect(result).toBe(false); + }); + + it('should return false when plugins array does not contain backstage plugin', async () => { + mockDir.setContent({ + '.yarnrc.yml': ` +plugins: + - path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs + - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs +`, + }); + + const result = await getHasYarnPlugin(); + expect(result).toBe(false); + }); + + it('should return true when backstage plugin is present', async () => { + mockDir.setContent({ + '.yarnrc.yml': ` +plugins: + - path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs + - path: .yarn/plugins/@yarnpkg/plugin-backstage.cjs + - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs +`, + }); + + const result = await getHasYarnPlugin(); + expect(result).toBe(true); + }); + + it('should return true when backstage plugin is the only plugin', async () => { + mockDir.setContent({ + '.yarnrc.yml': ` +plugins: + - path: .yarn/plugins/@yarnpkg/plugin-backstage.cjs +`, + }); + + const result = await getHasYarnPlugin(); + expect(result).toBe(true); + }); + + it('should throw error when .yarnrc.yml has invalid content', async () => { + mockDir.setContent({ + '.yarnrc.yml': 'invalid: yaml: content: [', + }); + + await expect(getHasYarnPlugin()).rejects.toThrow(); + }); + + it('should throw error when .yarnrc.yml has unexpected structure', async () => { + mockDir.setContent({ + '.yarnrc.yml': ` +plugins: "not an array" +`, + }); + + await expect(getHasYarnPlugin()).rejects.toThrow( + 'Unexpected content in .yarnrc.yml', + ); + }); + + it('should handle plugins with different structure', async () => { + mockDir.setContent({ + '.yarnrc.yml': ` +plugins: + - path: .yarn/plugins/@yarnpkg/plugin-backstage.cjs + - path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs +`, + }); + + const result = await getHasYarnPlugin(); + expect(result).toBe(true); + }); +}); diff --git a/packages/cli/src/lib/yarnPlugin.ts b/packages/cli/src/lib/yarnPlugin.ts new file mode 100644 index 0000000000..5ad49b2524 --- /dev/null +++ b/packages/cli/src/lib/yarnPlugin.ts @@ -0,0 +1,66 @@ +/* + * 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 fs from 'fs-extra'; +import yaml from 'yaml'; +import z from 'zod'; +import { paths } from './paths'; + +const yarnRcSchema = z.object({ + plugins: z + .array( + z.object({ + path: z.string(), + }), + ) + .optional(), +}); + +/** + * Detects whether the Backstage Yarn plugin is installed in the target repository. + * + * @returns Promise - true if the plugin is installed, false otherwise + */ +export async function getHasYarnPlugin(): Promise { + const yarnRcPath = paths.resolveTargetRoot('.yarnrc.yml'); + const yarnRcContent = await fs.readFile(yarnRcPath, 'utf-8').catch(e => { + if (e.code === 'ENOENT') { + // gracefully continue in case the file doesn't exist + return ''; + } + throw e; + }); + + if (!yarnRcContent) { + return false; + } + + const parseResult = yarnRcSchema.safeParse(yaml.parse(yarnRcContent)); + + if (!parseResult.success) { + throw new Error( + `Unexpected content in .yarnrc.yml: ${parseResult.error.toString()}`, + ); + } + + const yarnRc = parseResult.data; + + const backstagePlugin = yarnRc.plugins?.some( + plugin => plugin.path === '.yarn/plugins/@yarnpkg/plugin-backstage.cjs', + ); + + return Boolean(backstagePlugin); +} diff --git a/packages/cli/src/modules/build/commands/package/start/command.ts b/packages/cli/src/modules/build/commands/package/start/command.ts index 05fc668356..b38957f488 100644 --- a/packages/cli/src/modules/build/commands/package/start/command.ts +++ b/packages/cli/src/modules/build/commands/package/start/command.ts @@ -23,6 +23,7 @@ import { paths } from '../../../../../lib/paths'; export async function command(opts: OptionValues): Promise { await startPackage({ role: await findRoleFromCommand(opts), + entrypoint: opts.entrypoint, targetDir: paths.targetDir, configPaths: opts.config as string[], checksEnabled: Boolean(opts.check), diff --git a/packages/cli/src/modules/build/commands/package/start/startPackage.test.ts b/packages/cli/src/modules/build/commands/package/start/startPackage.test.ts new file mode 100644 index 0000000000..ca153adb22 --- /dev/null +++ b/packages/cli/src/modules/build/commands/package/start/startPackage.test.ts @@ -0,0 +1,89 @@ +/* + * 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 { createMockDirectory } from '@backstage/backend-test-utils'; +import { resolveEntryPath } from './startPackage'; + +describe('resolveEntryPath', () => { + const mockDir = createMockDirectory(); + + afterEach(() => { + mockDir.clear(); + }); + + it('should remove file extensions', () => { + mockDir.setContent({ + 'dev/custom.tsx': '// dev app code', + }); + + const result = resolveEntryPath('dev/custom.tsx', mockDir.path); + + expect(result).toBe('dev/custom'); + }); + + it('should remove trailing slashes', () => { + mockDir.setContent({ + 'dev/alpha.ts': '// dev app code', + }); + + const result = resolveEntryPath('dev/alpha/', mockDir.path); + + expect(result).toBe('dev/alpha'); + }); + + it('should handle multiple dots in filename', () => { + mockDir.setContent({ + 'index.alpha.ts': 'export const data = {};', + }); + + const result = resolveEntryPath('index.alpha.ts', mockDir.path); + + expect(result).toBe('index.alpha'); + }); + + it('should handle simple directory names', () => { + mockDir.setContent({ + 'dev/index.ts': '// dev app code', + }); + + const result = resolveEntryPath('dev', mockDir.path); + + expect(result).toBe('dev/index'); + }); + + it('should handle nested directory paths', () => { + mockDir.setContent({ + 'dev/alpha/index.ts': '// dev app code', + }); + + const result = resolveEntryPath('dev/alpha', mockDir.path); + + expect(result).toBe('dev/alpha/index'); + }); + + it('should return the file when there is a directory with the same name', () => { + mockDir.setContent({ + 'dev/alpha.ts': '// dev app code', + 'dev/app-config.yaml': '// dev app config', + 'dev/alpha/index.ts': '// dev app code', + 'dev/alpha/app-config.yaml': '// dev app config', + }); + + const result = resolveEntryPath('dev/alpha', mockDir.path); + + expect(result).toBe('dev/alpha'); + }); +}); diff --git a/packages/cli/src/modules/build/commands/package/start/startPackage.ts b/packages/cli/src/modules/build/commands/package/start/startPackage.ts index 10f705ba4d..b49fd482e5 100644 --- a/packages/cli/src/modules/build/commands/package/start/startPackage.ts +++ b/packages/cli/src/modules/build/commands/package/start/startPackage.ts @@ -17,9 +17,24 @@ import { PackageRole } from '@backstage/cli-node'; import { startBackend, startBackendPlugin } from './startBackend'; import { startFrontend } from './startFrontend'; +import { parse, resolve, join } from 'path'; +import { glob } from 'glob'; + +export function resolveEntryPath( + entrypoint: string = 'dev', + targetDir: string, +): string { + const { dir: entryDir, name: entryName } = parse(entrypoint); + const [entryFile] = glob.sync(`${resolve(targetDir, entryDir, entryName)}.*`); + if (entryFile) { + return join(entryDir, entryName); + } + return join(entryDir, entryName, 'index'); +} export async function startPackage(options: { role: PackageRole; + entrypoint?: string; targetDir: string; configPaths: string[]; checksEnabled: boolean; @@ -45,8 +60,8 @@ export async function startPackage(options: { case 'frontend-plugin': case 'frontend-plugin-module': return startFrontend({ - entry: 'dev/index', ...options, + entry: resolveEntryPath(options.entrypoint, options.targetDir), }); case 'frontend-dynamic-container' as PackageRole: // experimental return startFrontend({ diff --git a/packages/cli/src/modules/build/index.ts b/packages/cli/src/modules/build/index.ts index fd31a6c782..5382adfec9 100644 --- a/packages/cli/src/modules/build/index.ts +++ b/packages/cli/src/modules/build/index.ts @@ -137,6 +137,10 @@ export const buildPlugin = createCliPlugin({ '--link ', 'Link an external workspace for module resolution', ) + .option( + '--entrypoint ', + 'The entrypoint to start from, relative to the package root. Can point to either a file (without extension) or a directory (in which case the index file in that directory is used). Defaults to "dev"', + ) .action(lazy(() => import('./commands/package/start'), 'command')); await defaultCommand.parseAsync(args, { from: 'user' }); diff --git a/packages/cli/src/modules/migrate/commands/versions/bump.ts b/packages/cli/src/modules/migrate/commands/versions/bump.ts index 391ccf8f22..39cfeddbd5 100644 --- a/packages/cli/src/modules/migrate/commands/versions/bump.ts +++ b/packages/cli/src/modules/migrate/commands/versions/bump.ts @@ -21,11 +21,10 @@ import chalk from 'chalk'; import { minimatch } from 'minimatch'; import semver from 'semver'; import { OptionValues } from 'commander'; -import yaml from 'yaml'; -import z from 'zod'; import { isError, NotFoundError } from '@backstage/errors'; import { resolve as resolvePath } from 'path'; import { paths } from '../../../../lib/paths'; +import { getHasYarnPlugin } from '../../../../lib/yarnPlugin'; import { mapDependencies, fetchPackageInfo, @@ -497,42 +496,3 @@ async function asLockfileVersion(version: string) { return version; } - -const yarnRcSchema = z.object({ - plugins: z - .array( - z.object({ - path: z.string(), - }), - ) - .optional(), -}); - -async function getHasYarnPlugin() { - const yarnRcPath = paths.resolveTargetRoot('.yarnrc.yml'); - const yarnRcContent = await fs.readFile(yarnRcPath, 'utf-8').catch(e => { - if (e.code === 'ENOENT') { - // gracefully continue in case the file doesn't exist - return ''; - } - throw e; - }); - - if (!yarnRcContent) { - return false; - } - - const parseResult = yarnRcSchema.safeParse(yaml.parse(yarnRcContent)); - - if (!parseResult.success) { - throw new Error( - `Unexpected content in .yarnrc.yml: ${parseResult.error.toString()}`, - ); - } - - const yarnRc = parseResult.data; - - return yarnRc.plugins?.some( - plugin => plugin.path === '.yarn/plugins/@yarnpkg/plugin-backstage.cjs', - ); -} diff --git a/packages/cli/src/modules/new/lib/execution/PortableTemplater.ts b/packages/cli/src/modules/new/lib/execution/PortableTemplater.ts index 519f7c6dbc..9dfd60baba 100644 --- a/packages/cli/src/modules/new/lib/execution/PortableTemplater.ts +++ b/packages/cli/src/modules/new/lib/execution/PortableTemplater.ts @@ -27,6 +27,7 @@ import lowerFirst from 'lodash/lowerFirst'; import { Lockfile } from '../../../../lib/versioning'; import { paths } from '../../../../lib/paths'; import { createPackageVersionProvider } from '../../../../lib/version'; +import { getHasYarnPlugin } from '../../../../lib/yarnPlugin'; const builtInHelpers = { camelCase, @@ -53,7 +54,10 @@ export class PortableTemplater { /* ignored */ } - const versionProvider = createPackageVersionProvider(lockfile); + const hasYarnPlugin = await getHasYarnPlugin(); + const versionProvider = createPackageVersionProvider(lockfile, { + preferBackstageProtocol: hasYarnPlugin, + }); const templater = new PortableTemplater( { diff --git a/packages/config-loader/CHANGELOG.md b/packages/config-loader/CHANGELOG.md index 872136d5a2..b29be4a927 100644 --- a/packages/config-loader/CHANGELOG.md +++ b/packages/config-loader/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/config-loader +## 1.10.4-next.0 + +### Patch Changes + +- b45b094: Allow colon to be used as config key. +- Updated dependencies + - @backstage/config@1.3.4-next.0 + ## 1.10.3 ### Patch Changes diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index f8d103e97b..7c8e46f3f8 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/config-loader", - "version": "1.10.3", + "version": "1.10.5-next.0", "description": "Config loading functionality used by Backstage backend, and CLI", "backstage": { "role": "node-library" diff --git a/packages/config-loader/src/sources/EnvConfigSource.ts b/packages/config-loader/src/sources/EnvConfigSource.ts index d724f00c6d..988110b370 100644 --- a/packages/config-loader/src/sources/EnvConfigSource.ts +++ b/packages/config-loader/src/sources/EnvConfigSource.ts @@ -85,7 +85,7 @@ export class EnvConfigSource implements ConfigSource { const ENV_PREFIX = 'APP_CONFIG_'; // Update the same pattern in config package if this is changed -const CONFIG_KEY_PART_PATTERN = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/i; +const CONFIG_KEY_PART_PATTERN = /^[a-z][a-z0-9]*(?:[-_:][a-z0-9]+)*$/i; /** * Read runtime configuration from the environment. diff --git a/packages/config/CHANGELOG.md b/packages/config/CHANGELOG.md index bf9455a84e..43bcc0551b 100644 --- a/packages/config/CHANGELOG.md +++ b/packages/config/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/config +## 1.3.4-next.0 + +### Patch Changes + +- b45b094: Allow colon to be used as config key. + ## 1.3.3 ### Patch Changes diff --git a/packages/config/package.json b/packages/config/package.json index 4ec4038e26..87e69d0157 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/config", - "version": "1.3.3", + "version": "1.3.5-next.0", "description": "Config API used by Backstage core, backend, and CLI", "backstage": { "role": "common-library" diff --git a/packages/config/src/reader.test.ts b/packages/config/src/reader.test.ts index 3f6b9ac40a..bb16fbeaa1 100644 --- a/packages/config/src/reader.test.ts +++ b/packages/config/src/reader.test.ts @@ -43,6 +43,7 @@ const DATA = { null: null, string: 'string', strings: ['string1', 'string2'], + 'with:colon': 'yes', }, nestlings: [{ boolean: true }, { string: 'string' }, { number: 42 }] as {}[], }; @@ -57,6 +58,7 @@ function expectValidValues(config: ConfigReader) { expect(config.has('nested.one')).toBe(true); expect(config.has('nested.missing')).toBe(false); expect(config.has('nested.null')).toBe(false); + expect(config.has('nested.with:colon')).toBe(true); expect(config.getNumber('zero')).toBe(0); expect(config.getNumber('one')).toBe(1); expect(config.getNumber('zeroString')).toBe(0); @@ -84,8 +86,11 @@ function expectValidValues(config: ConfigReader) { null: undefined, string: 'string', strings: ['string1', 'string2'], + 'with:colon': 'yes', }); expect(config.getConfig('nested').getString('string')).toBe('string'); + expect(config.getString('nested.string')).toBe('string'); + expect(config.getString('nested.with:colon')).toBe('yes'); expect(config.getOptionalConfig('nested')!.getStringArray('strings')).toEqual( ['string1', 'string2'], ); diff --git a/packages/config/src/reader.ts b/packages/config/src/reader.ts index 2e83de9bf2..7ad0cf738d 100644 --- a/packages/config/src/reader.ts +++ b/packages/config/src/reader.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { JsonValue, JsonObject } from '@backstage/types'; +import { JsonObject, JsonValue } from '@backstage/types'; import { AppConfig, Config } from './types'; // Update the same pattern in config-loader package if this is changed -const CONFIG_KEY_PART_PATTERN = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/i; +const CONFIG_KEY_PART_PATTERN = /^[a-z][a-z0-9]*(?:[-_:][a-z0-9]+)*$/i; function isObject(value: JsonValue | undefined): value is JsonObject { return typeof value === 'object' && value !== null && !Array.isArray(value); diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md index 05bdf33224..334a36acb3 100644 --- a/packages/core-app-api/CHANGELOG.md +++ b/packages/core-app-api/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/core-app-api +## 1.19.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/core-plugin-api@1.11.1-next.0 + ## 1.19.0 ### Minor Changes diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index 8a52a71867..d4a4dce17d 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.0", + "version": "1.19.1-next.0", "description": "Core app API used by Backstage apps", "backstage": { "role": "web-library" diff --git a/packages/core-compat-api/CHANGELOG.md b/packages/core-compat-api/CHANGELOG.md index eb4c7d051f..f0cb3b6878 100644 --- a/packages/core-compat-api/CHANGELOG.md +++ b/packages/core-compat-api/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/core-compat-api +## 0.5.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/plugin-catalog-react@1.21.2-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + +## 0.5.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/core-plugin-api@1.11.0 + - @backstage/version-bridge@1.0.11 + ## 0.5.2 ### Patch Changes diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json index acab8947c8..0bbe284db9 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.2", + "version": "0.5.3-next.1", "backstage": { "role": "web-library" }, diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md index 63e6e45065..807d844712 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/core-components +## 0.18.2-next.2 + +### Patch Changes + +- 95935fb: Fixed dependency graph automatically scrolling forever +- Updated dependencies + - @backstage/theme@0.6.9-next.0 + +## 0.18.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/core-plugin-api@1.11.1-next.0 + +## 0.18.2-next.0 + +### Patch Changes + +- d493126: Swap base token for semantic token in ItemCardHeader to ensure readability in light mode. +- 6981ae6: Fixed DependencyGraph `svg` size not adapting to the container size +- Updated dependencies + - @backstage/config@1.3.3 + - @backstage/core-plugin-api@1.11.0 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.8 + - @backstage/version-bridge@1.0.11 + ## 0.18.0 ### Minor Changes diff --git a/packages/core-components/package.json b/packages/core-components/package.json index d369052e99..3418bbe033 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/core-components", - "version": "0.18.0", + "version": "0.18.2-next.2", "description": "Core components used by Backstage plugins and apps", "backstage": { "role": "web-library" diff --git a/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx b/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx index 411a0e2c57..88cb6ab5e8 100644 --- a/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx +++ b/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx @@ -22,6 +22,9 @@ import { useRef, useState, } from 'react'; +import useMeasure from 'react-use/esm/useMeasure'; +import classNames from 'classnames'; +import { once } from 'lodash'; import * as d3Zoom from 'd3-zoom'; import * as d3Selection from 'd3-selection'; import useTheme from '@material-ui/core/styles/useTheme'; @@ -41,11 +44,18 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { coreComponentsTranslationRef } from '../../translation'; const useStyles = makeStyles((theme: Theme) => ({ + fullscreenButton: { + position: 'absolute', + right: 0, + }, root: { overflow: 'hidden', minHeight: '100%', minWidth: '100%', }, + fixedHeight: { + maxHeight: '100%', + }, fullscreen: { backgroundColor: theme.palette.background.paper, }, @@ -268,9 +278,12 @@ export function DependencyGraph( const maxWidth = Math.max(graphWidth, containerWidth); const maxHeight = Math.max(graphHeight, containerHeight); - const minHeight = Math.min(graphHeight, containerHeight); - const scalableHeight = fit === 'grow' ? maxHeight : minHeight; + const [_measureRef] = useMeasure(); + const measureRef = once(_measureRef); + + const scalableHeight = + fit === 'grow' && !fullScreenHandle.active ? maxHeight : '100%'; const containerRef = useMemo( () => @@ -278,6 +291,8 @@ export function DependencyGraph( if (!root) { return; } + measureRef(root); + // Set up zooming + panning const node: SVGSVGElement = root.querySelector( `svg#${DEPENDENCY_GRAPH_SVG}`, @@ -321,14 +336,20 @@ export function DependencyGraph( const { width: newContainerWidth, height: newContainerHeight } = root.getBoundingClientRect(); - if (containerWidth !== newContainerWidth) { + if ( + containerWidth !== newContainerWidth && + newContainerWidth <= maxWidth + ) { setContainerWidth(newContainerWidth); } - if (containerHeight !== newContainerHeight) { + if ( + containerHeight !== newContainerHeight && + newContainerHeight <= maxHeight + ) { setContainerHeight(newContainerHeight); } }, 100), - [containerHeight, containerWidth, maxWidth, maxHeight, zoom], + [measureRef, containerHeight, containerWidth, maxWidth, maxHeight, zoom], ); const setNodesAndEdges = useCallback(() => { @@ -431,43 +452,51 @@ export function DependencyGraph( updateGraph, ]); - function setNode(id: string, node: Types.DependencyNode) { - graph.current.setNode(id, node); - updateGraph(); - return graph.current; - } + const setNode = useCallback( + (id: string, node: Types.DependencyNode) => { + graph.current.setNode(id, node); + updateGraph(); + return graph.current; + }, + [updateGraph], + ); - function setEdge(id: dagre.Edge, edge: Types.DependencyEdge) { - graph.current.setEdge(id, edge); - updateGraph(); - return graph.current; - } + const setEdge = useCallback( + (id: dagre.Edge, edge: Types.DependencyEdge) => { + graph.current.setEdge(id, edge); + updateGraph(); + return graph.current; + }, + [updateGraph], + ); return ( -
- - {allowFullscreen && ( - - - {fullScreenHandle.active ? ( - - ) : ( - - )} - - - )} + + {allowFullscreen && ( + + + {fullScreenHandle.active ? ( + + ) : ( + + )} + + + )} +
( height={graphHeight} y={maxHeight / 2 - graphHeight / 2} x={maxWidth / 2 - graphWidth / 2} - viewBox={`0 0 ${graphWidth} ${graphHeight}`} + viewBox={`-25 -25 ${graphWidth + 50} ${graphHeight + 50}`} > {graphEdges.map(e => { const edge = graph.current.edge(e) as GraphEdge; @@ -531,7 +560,7 @@ export function DependencyGraph( - -
+
+ ); } diff --git a/packages/core-components/src/layout/ItemCard/ItemCardHeader.tsx b/packages/core-components/src/layout/ItemCard/ItemCardHeader.tsx index b21ac53cf6..115c9e89e1 100644 --- a/packages/core-components/src/layout/ItemCard/ItemCardHeader.tsx +++ b/packages/core-components/src/layout/ItemCard/ItemCardHeader.tsx @@ -30,7 +30,7 @@ export type ItemCardHeaderClassKey = 'root'; const styles = (theme: Theme) => createStyles({ root: { - color: theme.palette.common.white, + color: theme.palette.text.primary, padding: theme.spacing(2, 2, 3), backgroundImage: theme.getPageTheme({ themeId: 'card' }).backgroundImage, backgroundPosition: 0, diff --git a/packages/core-plugin-api/CHANGELOG.md b/packages/core-plugin-api/CHANGELOG.md index 61291f8e05..89ad055230 100644 --- a/packages/core-plugin-api/CHANGELOG.md +++ b/packages/core-plugin-api/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/core-plugin-api +## 1.11.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + ## 1.11.0 ### Minor Changes diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json index 6e62c66c3a..d019090bb4 100644 --- a/packages/core-plugin-api/package.json +++ b/packages/core-plugin-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/core-plugin-api", - "version": "1.11.0", + "version": "1.11.1-next.0", "description": "Core API used by Backstage plugins", "backstage": { "role": "web-library" diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 5d317d4afa..baa76026b6 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/create-app +## 0.7.5-next.2 + +### Patch Changes + +- Bumped create-app version. + +## 0.7.5-next.1 + +### Patch Changes + +- Bumped create-app version. + +## 0.7.5-next.0 + +### Patch Changes + +- Bumped create-app version. +- Updated dependencies + - @backstage/cli-common@0.1.15 + ## 0.7.4 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 263d65e5c8..eac44e445a 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/create-app", - "version": "0.7.4", + "version": "0.7.5-next.2", "description": "A CLI that helps you create your own Backstage app", "backstage": { "role": "cli" diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index 47ec659481..4a0af6926c 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,40 @@ # @backstage/dev-utils +## 1.1.15-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.2 + - @backstage/theme@0.6.9-next.0 + - @backstage/app-defaults@1.7.1-next.2 + +## 1.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.19.1-next.0 + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/integration-react@1.2.11-next.1 + - @backstage/app-defaults@1.7.1-next.1 + - @backstage/plugin-catalog-react@1.21.2-next.1 + +## 1.1.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/core-components@0.18.2-next.0 + - @backstage/app-defaults@1.7.1-next.0 + - @backstage/integration-react@1.2.11-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-app-api@1.19.0 + - @backstage/core-plugin-api@1.11.0 + - @backstage/theme@0.6.8 + ## 1.1.14 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 40882a898b..9540f79c9e 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/dev-utils", - "version": "1.1.14", + "version": "1.1.15-next.2", "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 471c2fc53e..fae3f16eb4 100644 --- a/packages/e2e-test/CHANGELOG.md +++ b/packages/e2e-test/CHANGELOG.md @@ -1,5 +1,14 @@ # e2e-test +## 0.2.33-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.7.5-next.0 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + ## 0.2.32 ### Patch Changes diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json index 6b255f43b1..80a2ea7d3f 100644 --- a/packages/e2e-test/package.json +++ b/packages/e2e-test/package.json @@ -1,6 +1,6 @@ { "name": "e2e-test", - "version": "0.2.32", + "version": "0.2.33-next.0", "description": "E2E test for verifying Backstage packages", "backstage": { "role": "cli" diff --git a/packages/frontend-app-api/CHANGELOG.md b/packages/frontend-app-api/CHANGELOG.md index 1a23d4b535..780e62fbe8 100644 --- a/packages/frontend-app-api/CHANGELOG.md +++ b/packages/frontend-app-api/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/frontend-app-api +## 0.13.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/core-app-api@1.19.1-next.0 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/frontend-defaults@0.3.2-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + +## 0.13.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-defaults@0.3.2-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.19.0 + - @backstage/core-plugin-api@1.11.0 + - @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-app-api/package.json b/packages/frontend-app-api/package.json index 3f2dab8571..707067b54f 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.0", + "version": "0.13.1-next.1", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-defaults/CHANGELOG.md b/packages/frontend-defaults/CHANGELOG.md index 65713fac5f..f60f948f3a 100644 --- a/packages/frontend-defaults/CHANGELOG.md +++ b/packages/frontend-defaults/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/frontend-defaults +## 0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/core-components@0.18.2-next.1 + - @backstage/frontend-app-api@0.13.1-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/plugin-app@0.3.1-next.1 + +## 0.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/plugin-app@0.3.1-next.0 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/frontend-app-api@0.13.1-next.0 + ## 0.3.1 ### Patch Changes diff --git a/packages/frontend-defaults/package.json b/packages/frontend-defaults/package.json index 6a8b100c0d..ab8f88b653 100644 --- a/packages/frontend-defaults/package.json +++ b/packages/frontend-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-defaults", - "version": "0.3.1", + "version": "0.3.2-next.1", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-defaults/src/createPublicSignInApp.test.tsx b/packages/frontend-defaults/src/createPublicSignInApp.test.tsx index 7e12499a1c..e80d058140 100644 --- a/packages/frontend-defaults/src/createPublicSignInApp.test.tsx +++ b/packages/frontend-defaults/src/createPublicSignInApp.test.tsx @@ -96,6 +96,7 @@ describe('createPublicSignInApp', () => {
; + /** Integration configuration for Azure Blob Storage */ + azureBlobStorage?: Array<{ + /** + * The name of the Azure Storage Account, e.g., "mystorageaccount". + */ + accountName?: string; + + /** + * The primary or secondary key for the Azure Storage Account. + * Only required if connectionString or SAS token are not specified. + * @visibility secret + */ + accountKey?: string; + + /** + * A Shared Access Signature (SAS) token for limited access to resources. + * @visibility secret + */ + sasToken?: string; + + /** + * A full connection string for the Azure Storage Account. + * This includes the account name, key, and endpoint details. + * @visibility secret + */ + connectionString?: string; + + /** + * Optional endpoint suffix for custom domains or sovereign clouds. + * e.g., "core.windows.net" for public Azure or "core.usgovcloudapi.net" for US Government cloud. + */ + endpointSuffix?: string; + + /** + * Optional endpoint URL for custom domain. Uses default if not provided. + * @visibility frontend + */ + endpoint?: string; + /** + * Optional credential to use for Azure Active Directory authentication. + * @deepVisibility secret + */ + aadCredential?: { + /** + * The client ID of the Azure AD application. + */ + clientId: string; + + /** + * The tenant ID for Azure AD. + */ + tenantId: string; + + /** + * The client secret for the Azure AD application. + */ + clientSecret: string; + }; + }>; + /** * Integration configuration for Bitbucket * @deprecated replaced by bitbucketCloud and bitbucketServer diff --git a/packages/integration/package.json b/packages/integration/package.json index 68289e2079..2dbb4a32e8 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration", - "version": "1.18.0", + "version": "1.18.1-next.1", "description": "Helpers for managing integrations towards external systems", "backstage": { "role": "common-library" diff --git a/packages/repo-tools/CHANGELOG.md b/packages/repo-tools/CHANGELOG.md index d93d88571a..d8272e383a 100644 --- a/packages/repo-tools/CHANGELOG.md +++ b/packages/repo-tools/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/repo-tools +## 0.15.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/cli-node@0.2.14 + ## 0.15.2 ### Patch Changes diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index 0b0954da70..a536a95f4c 100644 --- a/packages/repo-tools/package.json +++ b/packages/repo-tools/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/repo-tools", - "version": "0.15.2", + "version": "0.15.3-next.0", "description": "CLI for Backstage repo tooling ", "backstage": { "role": "cli" diff --git a/packages/scaffolder-internal/CHANGELOG.md b/packages/scaffolder-internal/CHANGELOG.md index de8c725e03..e636e2ccc8 100644 --- a/packages/scaffolder-internal/CHANGELOG.md +++ b/packages/scaffolder-internal/CHANGELOG.md @@ -1,5 +1,13 @@ # @internal/scaffolder +## 0.0.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.19.2-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + ## 0.0.13 ### Patch Changes diff --git a/packages/scaffolder-internal/package.json b/packages/scaffolder-internal/package.json index abbd487103..9f490a8b62 100644 --- a/packages/scaffolder-internal/package.json +++ b/packages/scaffolder-internal/package.json @@ -1,6 +1,6 @@ { "name": "@internal/scaffolder", - "version": "0.0.13", + "version": "0.0.14-next.0", "backstage": { "role": "web-library", "inline": true diff --git a/packages/techdocs-cli-embedded-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md index 76a8f81a63..121bf3b763 100644 --- a/packages/techdocs-cli-embedded-app/CHANGELOG.md +++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md @@ -1,5 +1,54 @@ # techdocs-cli-embedded-app +## 0.2.113-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.34.4-next.2 + - @backstage/core-components@0.18.2-next.2 + - @backstage/theme@0.6.9-next.0 + - @backstage/app-defaults@1.7.1-next.2 + - @backstage/test-utils@1.7.12-next.1 + - @backstage/plugin-techdocs@1.15.1-next.2 + - @backstage/plugin-techdocs-react@1.3.4-next.1 + +## 0.2.113-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/cli@0.34.4-next.1 + - @backstage/core-app-api@1.19.1-next.0 + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/integration-react@1.2.11-next.1 + - @backstage/test-utils@1.7.12-next.0 + - @backstage/plugin-techdocs@1.15.1-next.1 + - @backstage/plugin-techdocs-react@1.3.4-next.1 + - @backstage/app-defaults@1.7.1-next.1 + - @backstage/plugin-catalog@1.31.4-next.1 + +## 0.2.113-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/cli@0.34.4-next.0 + - @backstage/plugin-catalog@1.31.4-next.0 + - @backstage/plugin-techdocs@1.15.1-next.0 + - @backstage/app-defaults@1.7.1-next.0 + - @backstage/integration-react@1.2.11-next.0 + - @backstage/plugin-techdocs-react@1.3.4-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.19.0 + - @backstage/core-plugin-api@1.11.0 + - @backstage/test-utils@1.7.11 + - @backstage/theme@0.6.8 + ## 0.2.112 ### Patch Changes diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index 09e974ed88..a4019eb918 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.112", + "version": "0.2.113-next.2", "backstage": { "role": "frontend" }, diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md index 1fb32bec51..2fe1e1101a 100644 --- a/packages/techdocs-cli/CHANGELOG.md +++ b/packages/techdocs-cli/CHANGELOG.md @@ -1,5 +1,25 @@ # @techdocs/cli +## 1.9.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-defaults@0.13.0-next.1 + - @backstage/plugin-techdocs-node@1.13.8-next.1 + +## 1.9.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.0-next.0 + - @backstage/plugin-techdocs-node@1.13.8-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.3 + ## 1.9.8 ### Patch Changes diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index 7ce62cd803..b46d0bc540 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -1,6 +1,6 @@ { "name": "@techdocs/cli", - "version": "1.9.8", + "version": "1.9.9-next.1", "description": "Utility CLI for managing TechDocs sites in Backstage.", "backstage": { "role": "cli" diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index 57e6d7e237..a7005cc96d 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/test-utils +## 1.7.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.6.9-next.0 + +## 1.7.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/core-app-api@1.19.1-next.0 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-permission-react@0.4.37-next.0 + ## 1.7.11 ### Patch Changes diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index aade054088..1eb1849d1f 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/test-utils", - "version": "1.7.11", + "version": "1.7.12-next.1", "description": "Utilities to test Backstage plugins and apps.", "backstage": { "role": "web-library" diff --git a/packages/theme/CHANGELOG.md b/packages/theme/CHANGELOG.md index 9cb3a6527b..5d531e0a9b 100644 --- a/packages/theme/CHANGELOG.md +++ b/packages/theme/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/theme +## 0.6.9-next.0 + +### Patch Changes + +- d5cbdba: The `UnifiedThemeProvider` now coordinates theme attributes on the document `body` in case multiple theme providers are rendered. + ## 0.6.8 ### Patch Changes diff --git a/packages/theme/package.json b/packages/theme/package.json index 3699d858a4..1ac8aeb9bb 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/theme", - "version": "0.6.8", + "version": "0.6.9-next.0", "description": "material-ui theme for use with Backstage.", "backstage": { "role": "web-library" diff --git a/packages/theme/src/unified/UnifiedThemeProvider.tsx b/packages/theme/src/unified/UnifiedThemeProvider.tsx index a80e36cf35..64694aff37 100644 --- a/packages/theme/src/unified/UnifiedThemeProvider.tsx +++ b/packages/theme/src/unified/UnifiedThemeProvider.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { ReactNode, useEffect } from 'react'; +import { ReactNode } from 'react'; import CssBaseline from '@material-ui/core/CssBaseline'; import { ThemeProvider, @@ -58,6 +58,8 @@ const generateV4ClassName = createGenerateClassName({ productionPrefix: 'jss4-', }); +import { useApplyThemeAttributes } from './useApplyThemeAttributes'; + /** * Provides themes for all Material UI versions supported by the provided unified theme. * @@ -70,18 +72,11 @@ export function UnifiedThemeProvider( const v4Theme = theme.getTheme('v4') as Mui4Theme; const v5Theme = theme.getTheme('v5') as Mui5Theme; - const themeMode = v4Theme ? v4Theme.palette.type : v5Theme?.palette.mode; - const themeName = 'backstage'; - useEffect(() => { - document.body.setAttribute('data-theme-mode', themeMode); - document.body.setAttribute('data-theme-name', themeName); - - return () => { - document.body.removeAttribute('data-theme-mode'); - document.body.removeAttribute('data-theme-name'); - }; - }, [themeMode, themeName]); + useApplyThemeAttributes( + v4Theme ? v4Theme.palette.type : v5Theme?.palette.mode, + 'backstage', + ); let cssBaseline: JSX.Element | undefined = undefined; if (!noCssBaseline) { diff --git a/packages/theme/src/unified/useApplyThemeAttributes.test.tsx b/packages/theme/src/unified/useApplyThemeAttributes.test.tsx new file mode 100644 index 0000000000..b6c1ec1852 --- /dev/null +++ b/packages/theme/src/unified/useApplyThemeAttributes.test.tsx @@ -0,0 +1,60 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import '@testing-library/jest-dom'; +import { renderHook } from '@testing-library/react'; +import { useApplyThemeAttributes } from './useApplyThemeAttributes'; + +describe('useApplyThemeAttributes', () => { + beforeEach(() => { + document.body.removeAttribute('data-unified-theme-stack'); + document.body.removeAttribute('data-theme-mode'); + document.body.removeAttribute('data-theme-name'); + }); + + it('pushes attributes on mount and pops on unmount', () => { + const { unmount } = renderHook(() => + useApplyThemeAttributes('light', 'one'), + ); + expect(document.body.getAttribute('data-theme-mode')).toBe('light'); + expect(document.body.getAttribute('data-theme-name')).toBe('one'); + expect( + JSON.parse(document.body.getAttribute('data-unified-theme-stack') || '[]') + .length, + ).toBe(1); + + unmount(); + expect(document.body.getAttribute('data-theme-mode')).toBeNull(); + expect(document.body.getAttribute('data-theme-name')).toBeNull(); + expect(document.body.getAttribute('data-unified-theme-stack')).toBeNull(); + }); + + it('stacks multiple mounts and applies top-most', () => { + const r1 = renderHook(() => useApplyThemeAttributes('light', 'one')); + const r2 = renderHook(() => useApplyThemeAttributes('dark', 'two')); + + expect(document.body.getAttribute('data-theme-mode')).toBe('dark'); + expect(document.body.getAttribute('data-theme-name')).toBe('two'); + + r2.unmount(); + expect(document.body.getAttribute('data-theme-mode')).toBe('light'); + expect(document.body.getAttribute('data-theme-name')).toBe('one'); + + r1.unmount(); + expect(document.body.getAttribute('data-theme-mode')).toBeNull(); + expect(document.body.getAttribute('data-theme-name')).toBeNull(); + }); +}); diff --git a/packages/theme/src/unified/useApplyThemeAttributes.ts b/packages/theme/src/unified/useApplyThemeAttributes.ts new file mode 100644 index 0000000000..8295d69ec6 --- /dev/null +++ b/packages/theme/src/unified/useApplyThemeAttributes.ts @@ -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 { useEffect } from 'react'; + +type ThemeFrame = { mode?: string; name?: string }; + +const STACK_ATTR = 'data-unified-theme-stack'; +const MODE_ATTR = 'data-theme-mode'; +const NAME_ATTR = 'data-theme-name'; + +function mutateAttrStack(mutator: (stack: ThemeFrame[]) => void) { + const stack = (() => { + const raw = document.body.getAttribute(STACK_ATTR); + if (!raw) { + return [] as ThemeFrame[]; + } + try { + const parsed = JSON.parse(raw); + return Array.isArray(parsed) ? (parsed as ThemeFrame[]) : []; + } catch { + return [] as ThemeFrame[]; + } + })(); + + mutator(stack); + + if (stack.length === 0) { + document.body.removeAttribute(STACK_ATTR); + } else { + document.body.setAttribute(STACK_ATTR, JSON.stringify(stack)); + } + + const top = stack[stack.length - 1]; + if (top?.mode) { + document.body.setAttribute(MODE_ATTR, String(top.mode)); + } else { + document.body.removeAttribute(MODE_ATTR); + } + if (top?.name) { + document.body.setAttribute(NAME_ATTR, String(top.name)); + } else { + document.body.removeAttribute(NAME_ATTR); + } +} + +export function useApplyThemeAttributes(themeMode: string, themeName: string) { + useEffect(() => { + mutateAttrStack(stack => { + stack.push({ mode: themeMode, name: themeName }); + }); + + return () => { + mutateAttrStack(stack => { + stack.pop(); + }); + }; + }, [themeMode, themeName]); +} diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md index 0fcd9d2ff6..2efdbb2b9e 100644 --- a/packages/ui/CHANGELOG.md +++ b/packages/ui/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/ui +## 0.7.2-next.1 + +### Patch Changes + +- a9b88be: Enable tooltips on disabled buttons with automatic wrapper +- 4adbb03: Avoid overriding onChange when spreading props + +## 0.7.2-next.0 + +### Patch Changes + +- 827340f: remove default selection of tab +- 9a47125: Improved SearchField component flex layout and animations. Fixed SearchField behavior in Header components by switching from width-based transitions to flex-basis transitions for better responsive behavior. Added new Storybook stories to test SearchField integration with Header component. + ## 0.7.1 ### Patch Changes diff --git a/packages/ui/css/styles.css b/packages/ui/css/styles.css index 88be061ba5..8aa99175b3 100644 --- a/packages/ui/css/styles.css +++ b/packages/ui/css/styles.css @@ -10523,6 +10523,8 @@ } .bui-SearchField { + flex: 1 0; + &[data-empty] { & .bui-InputClear { display: none; @@ -10530,49 +10532,81 @@ } &[data-start-collapsed="true"] { + flex: 0 auto; padding: 0; - transition: width .3s ease-in-out; + transition: flex-basis .3s ease-in-out; + + &[data-collapsed="true"] { + flex-basis: 200px; + } &[data-collapsed="false"] { cursor: pointer; &[data-size="medium"] { - width: 2.5rem; + flex-basis: 2.5rem; height: 2.5rem; } &[data-size="small"] { - width: 2rem; + flex-basis: 2rem; height: 2rem; } &[data-size="medium"] .bui-Input { - padding-left: 0; - &::placeholder { opacity: 0; } } &[data-size="small"] .bui-Input { - padding-left: 0; - &::placeholder { opacity: 0; } } - &[data-size="small"] .bui-InputIcon { - left: var(--bui-space-2); - } - - &[data-size="medium"] .bui-InputIcon { - left: 10px; + & .bui-InputWrapper { + & .bui-Input[data-icon] { + padding-right: 0; + } } } } } +.bui-SearchField .bui-Input { + transition: padding .3s ease-in-out, border-color .2s ease-in-out, outline-color .2s ease-in-out; + + &[data-hovered] { + border-color: var(--bui-border-hover); + } + + &[data-focused] { + border-color: var(--bui-border-pressed); + outline-width: 0; + } +} + +.bui-SearchField .bui-InputWrapper { + & .bui-Input[data-icon] { + padding-right: var(--bui-space-6); + } +} + +.bui-SearchField .bui-InputIcon { + justify-content: center; + display: flex; + left: 0; + + &[data-size="small"] { + width: var(--bui-space-8); + } + + &[data-size="medium"] { + width: var(--bui-space-10); + } +} + .bui-InputClear { cursor: pointer; color: var(--bui-fg-secondary); diff --git a/packages/ui/package.json b/packages/ui/package.json index c0a0e7c143..448905ab9d 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/ui", - "version": "0.7.1", + "version": "0.7.2-next.1", "backstage": { "role": "web-library" }, @@ -49,7 +49,7 @@ "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", "chalk": "^5.4.1", - "eslint-plugin-storybook": "^9.1.5", + "eslint-plugin-storybook": "^9.1.7", "glob": "^11.0.1", "globals": "^15.11.0", "lightningcss": "^1.29.1", @@ -57,7 +57,7 @@ "react": "^18.0.2", "react-dom": "^18.0.2", "react-router-dom": "^6.3.0", - "storybook": "^9.1.5" + "storybook": "^9.1.7" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", diff --git a/packages/ui/src/components/Button/Button.stories.tsx b/packages/ui/src/components/Button/Button.stories.tsx index db8e999f98..1604c04eb4 100644 --- a/packages/ui/src/components/Button/Button.stories.tsx +++ b/packages/ui/src/components/Button/Button.stories.tsx @@ -19,6 +19,7 @@ import { Button } from './Button'; import { Flex } from '../Flex'; import { Text } from '../Text'; import { Icon } from '../Icon'; +import { Tooltip, TooltipTrigger } from '../Tooltip'; const meta = { title: 'Backstage UI/Button', @@ -216,3 +217,12 @@ export const Playground: Story = { ), }; + +export const DisabledWithTooltips: Story = { + render: () => ( + + + Why this is disabled + + ), +}; diff --git a/packages/ui/src/components/Button/Button.tsx b/packages/ui/src/components/Button/Button.tsx index 40879e51ed..9f658fd00a 100644 --- a/packages/ui/src/components/Button/Button.tsx +++ b/packages/ui/src/components/Button/Button.tsx @@ -16,7 +16,7 @@ import clsx from 'clsx'; import { forwardRef, Ref } from 'react'; -import { Button as RAButton } from 'react-aria-components'; +import { Button as RAButton, Focusable } from 'react-aria-components'; import type { ButtonProps } from './types'; import { useStyles } from '../../hooks/useStyles'; @@ -30,6 +30,7 @@ export const Button = forwardRef( iconEnd, children, className, + isDisabled, ...rest } = props; @@ -38,18 +39,29 @@ export const Button = forwardRef( variant, }); - return ( + const btn = ( {iconStart} {children} {iconEnd} ); + + return isDisabled ? ( + + + {btn} + + + ) : ( + btn + ); }, ); diff --git a/packages/ui/src/components/Header/Header.stories.tsx b/packages/ui/src/components/Header/Header.stories.tsx index 7ae834f82e..6ccda8bb50 100644 --- a/packages/ui/src/components/Header/Header.stories.tsx +++ b/packages/ui/src/components/Header/Header.stories.tsx @@ -57,14 +57,17 @@ const tabs: HeaderTab[] = [ { id: 'overview', label: 'Overview', + href: '/overview', }, { id: 'checks', label: 'Checks', + href: '/checks', }, { id: 'tracks', label: 'Tracks', + href: '/tracks', }, { id: 'campaigns', @@ -82,14 +85,17 @@ const tabs2: HeaderTab[] = [ { id: 'Banana', label: 'Banana', + href: '/banana', }, { id: 'Apple', label: 'Apple', + href: '/apple', }, { id: 'Orange', label: 'Orange', + href: '/orange', }, ]; diff --git a/packages/ui/src/components/SearchField/SearchField.stories.tsx b/packages/ui/src/components/SearchField/SearchField.stories.tsx index 14f265b243..7cb89dfbfb 100644 --- a/packages/ui/src/components/SearchField/SearchField.stories.tsx +++ b/packages/ui/src/components/SearchField/SearchField.stories.tsx @@ -23,6 +23,8 @@ import { FieldLabel } from '../FieldLabel'; import { ButtonIcon } from '../ButtonIcon'; import { RiCactusLine } from '@remixicon/react'; import { Button } from '../Button'; +import { Header } from '../Header'; +import { MemoryRouter } from 'react-router-dom'; const meta = { title: 'Backstage UI/SearchField', @@ -49,6 +51,7 @@ export const Default: Story = { style: { maxWidth: '300px', }, + 'aria-label': 'Search', }, }; @@ -170,6 +173,77 @@ export const StartCollapsed: Story = { ), }; +export const InHeader: Story = { + decorators: [ + Story => ( + + + + ), + ], + render: args => ( + <> +
+ } + size="small" + variant="secondary" + /> + + } + size="small" + variant="secondary" + /> + + } + /> + + ), +}; + +export const StartCollapsedInHeader: Story = { + args: { + ...StartCollapsed.args, + }, + decorators: [ + Story => ( + + + + ), + ], + render: args => ( + <> +
+ } + size="small" + variant="secondary" + /> + + } + size="small" + variant="secondary" + /> + + } + /> + + ), +}; + export const StartCollapsedWithButtons: Story = { args: { ...StartCollapsed.args, @@ -177,15 +251,46 @@ export const StartCollapsedWithButtons: Story = { render: args => ( - } size="small" variant="secondary" /> + } + size="small" + variant="secondary" + /> - } size="medium" variant="secondary" /> + } + size="medium" + variant="secondary" + /> ), }; + +export const StartCollapsedWithOnChange: Story = { + args: { + ...StartCollapsed.args, + }, + render: args => { + const handleChange = (value: string) => { + console.log('Search value:', value); + }; + + return ( + + + + ); + }, +}; diff --git a/packages/ui/src/components/SearchField/SearchField.styles.css b/packages/ui/src/components/SearchField/SearchField.styles.css index 062757f38f..a13c455b44 100644 --- a/packages/ui/src/components/SearchField/SearchField.styles.css +++ b/packages/ui/src/components/SearchField/SearchField.styles.css @@ -15,6 +15,9 @@ */ .bui-SearchField { + flex: 1; + flex-shrink: 0; + &[data-empty] { .bui-InputClear { display: none; @@ -22,49 +25,82 @@ } &[data-start-collapsed='true'] { - transition: width 0.3s ease-in-out; + transition: flex-basis 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'] { - width: 2.5rem; + flex-basis: 2.5rem; height: 2.5rem; } &[data-size='small'] { - width: 2rem; + flex-basis: 2rem; height: 2rem; } &[data-size='medium'] .bui-Input { - padding-left: 0; - &::placeholder { opacity: 0; } } &[data-size='small'] .bui-Input { - padding-left: 0; - &::placeholder { opacity: 0; } } - &[data-size='small'] .bui-InputIcon { - left: var(--bui-space-2); - } - - &[data-size='medium'] .bui-InputIcon { - left: 10px; + .bui-InputWrapper { + .bui-Input[data-icon] { + padding-right: 0px; + } } } } } +.bui-SearchField .bui-Input { + transition: padding 0.3s ease-in-out, border-color 0.2s ease-in-out, + outline-color 0.2s ease-in-out; + + &[data-hovered] { + border-color: var(--bui-border-hover); + } + + &[data-focused] { + border-color: var(--bui-border-pressed); + outline-width: 0px; + } +} + +.bui-SearchField .bui-InputWrapper { + .bui-Input[data-icon] { + padding-right: var(--bui-space-6); + } +} + +.bui-SearchField .bui-InputIcon { + left: 0; + display: flex; + justify-content: center; + + &[data-size='small'] { + width: var(--bui-space-8); + } + + &[data-size='medium'] { + width: var(--bui-space-10); + } +} + .bui-InputClear { position: absolute; right: 0; diff --git a/packages/ui/src/components/SearchField/SearchField.tsx b/packages/ui/src/components/SearchField/SearchField.tsx index e497cecd49..1c4bb7fa18 100644 --- a/packages/ui/src/components/SearchField/SearchField.tsx +++ b/packages/ui/src/components/SearchField/SearchField.tsx @@ -39,6 +39,7 @@ export const SearchField = forwardRef( secondaryLabel, description, isRequired, + onChange, placeholder = 'Search', startCollapsed = false, 'aria-label': ariaLabel, diff --git a/packages/ui/src/components/Tabs/Tabs.tsx b/packages/ui/src/components/Tabs/Tabs.tsx index d4c820e529..dacb355fc9 100644 --- a/packages/ui/src/components/Tabs/Tabs.tsx +++ b/packages/ui/src/components/Tabs/Tabs.tsx @@ -117,7 +117,7 @@ export const Tabs = (props: TabsProps) => { } } } - return undefined; + return null; })(); if (!children) return null; diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index 4a40848ced..446f2ed89f 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,46 @@ # @backstage/plugin-api-docs +## 0.13.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/plugin-permission-react@0.4.37-next.0 + - @backstage/core-compat-api@0.5.3-next.1 + - @backstage/plugin-catalog@1.31.4-next.1 + - @backstage/plugin-catalog-react@1.21.2-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/plugin-catalog-common@1.1.6-next.0 + +## 0.13.0-next.0 + +### Minor Changes + +- b8a381e: Remove explicit dependency on `isomorphic-form-data`. + + This explicit dependency was added to address [an issue](https://github.com/swagger-api/swagger-ui/issues/7436) in the + dependency `swagger-ui-react`. That [issue has since been resolved](https://github.com/swagger-api/swagger-ui/issues/7436#issuecomment-889792304), + and `isomorphic-form-data` no longer needs to be declared. + + Additionally, this changeset updates the `swagger-ui-react` dependency to version `5.19.0` or higher, which includes + [compatibility](https://github.com/swagger-api/swagger-ui?tab=readme-ov-file#compatibility) with the latest versions of + the OpenAPI specification. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/core-components@0.18.2-next.0 + - @backstage/core-compat-api@0.5.3-next.0 + - @backstage/plugin-catalog@1.31.4-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.11.0 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + ## 0.12.11 ### Patch Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index febec4ff08..1aa27b8770 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-api-docs", - "version": "0.12.11", + "version": "0.13.0-next.1", "description": "A Backstage plugin that helps represent API entities in the frontend", "backstage": { "role": "frontend-plugin", @@ -71,8 +71,7 @@ "graphql": "^16.0.0", "graphql-config": "^5.0.2", "graphql-ws": "^5.4.1", - "isomorphic-form-data": "^2.0.0", - "swagger-ui-react": "^5.0.0" + "swagger-ui-react": "^5.27.1" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md index 02039c933e..c4f1db2d8c 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-app-backend +## 0.5.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.4-next.0 + - @backstage/config@1.3.4-next.0 + - @backstage/plugin-app-node@0.1.38-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + ## 0.5.6 ### Patch Changes diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index 609cf1c543..4d4184e745 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.6", + "version": "0.5.7-next.0", "description": "A Backstage backend plugin that serves the Backstage frontend app", "backstage": { "role": "backend-plugin", diff --git a/plugins/app-node/CHANGELOG.md b/plugins/app-node/CHANGELOG.md index ea9532aca5..85d63c28f0 100644 --- a/plugins/app-node/CHANGELOG.md +++ b/plugins/app-node/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-app-node +## 0.1.38-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + ## 0.1.37 ### Patch Changes diff --git a/plugins/app-node/package.json b/plugins/app-node/package.json index f11a84bc55..68f07f9a22 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.37", + "version": "0.1.38-next.0", "description": "Node.js library for the app plugin", "backstage": { "role": "node-library", diff --git a/plugins/app-visualizer/CHANGELOG.md b/plugins/app-visualizer/CHANGELOG.md index bc5ac063cc..ea0b147aff 100644 --- a/plugins/app-visualizer/CHANGELOG.md +++ b/plugins/app-visualizer/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-app-visualizer +## 0.1.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.1 + +## 0.1.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/core-plugin-api@1.11.0 + ## 0.1.23 ### Patch Changes diff --git a/plugins/app-visualizer/package.json b/plugins/app-visualizer/package.json index 4affb81b50..fa30acefe2 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.23", + "version": "0.1.24-next.1", "description": "Visualizes the Backstage app structure", "backstage": { "role": "frontend-plugin", diff --git a/plugins/app/CHANGELOG.md b/plugins/app/CHANGELOG.md index c1026add86..8e3730b128 100644 --- a/plugins/app/CHANGELOG.md +++ b/plugins/app/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-app +## 0.3.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.2 + - @backstage/theme@0.6.9-next.0 + +## 0.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/integration-react@1.2.11-next.1 + - @backstage/plugin-permission-react@0.4.37-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.1 + +## 0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/integration-react@1.2.11-next.0 + - @backstage/core-plugin-api@1.11.0 + - @backstage/theme@0.6.8 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-permission-react@0.4.36 + ## 0.3.0 ### Minor Changes diff --git a/plugins/app/package.json b/plugins/app/package.json index d17d903b0a..d2e405e4b9 100644 --- a/plugins/app/package.json +++ b/plugins/app/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app", - "version": "0.3.0", + "version": "0.3.1-next.2", "backstage": { "role": "frontend-plugin", "pluginId": "app", diff --git a/plugins/app/src/alpha/appModulePublicSignIn.test.tsx b/plugins/app/src/alpha/appModulePublicSignIn.test.tsx index a40bf19dd6..787b8949e5 100644 --- a/plugins/app/src/alpha/appModulePublicSignIn.test.tsx +++ b/plugins/app/src/alpha/appModulePublicSignIn.test.tsx @@ -99,6 +99,7 @@ describe('appModulePublicSignIn', () => {
; } @@ -517,6 +518,7 @@ export class WithPagination< constructor( createUrl: (options: PaginationOptions) => URL, fetch: (url: URL) => Promise, + pagelen?: number | undefined, ); // (undocumented) getPage(options?: PaginationOptions): Promise; diff --git a/plugins/bitbucket-cloud-common/src/BitbucketCloudClient.test.ts b/plugins/bitbucket-cloud-common/src/BitbucketCloudClient.test.ts index 7558c92138..ed696ec6fb 100644 --- a/plugins/bitbucket-cloud-common/src/BitbucketCloudClient.test.ts +++ b/plugins/bitbucket-cloud-common/src/BitbucketCloudClient.test.ts @@ -79,6 +79,41 @@ describe('BitbucketCloudClient', () => { expect(results[0].file!.path).toEqual('path/to/file'); }); + it('searchCode with custom pagelen', async () => { + server.use( + rest.get( + `https://api.bitbucket.org/2.0/workspaces/ws/search/code`, + (req, res, ctx) => { + const pagelen = req.url.searchParams.get('pagelen'); + expect(pagelen).toBe('50'); + + const response: Models.SearchResultPage = { + values: [ + { + content_match_count: 1, + file: { + type: 'commit_file', + path: 'path/to/file', + }, + }, + ], + }; + return res(ctx.json(response)); + }, + ), + ); + + const pagination = client.searchCode('ws', 'query', undefined, 50); + + const results = []; + for await (const result of pagination.iterateResults()) { + results.push(result); + } + + expect(results).toHaveLength(1); + expect(results[0].file!.path).toEqual('path/to/file'); + }); + it('listRepositoriesByWorkspace', async () => { server.use( rest.get( diff --git a/plugins/bitbucket-cloud-common/src/BitbucketCloudClient.ts b/plugins/bitbucket-cloud-common/src/BitbucketCloudClient.ts index 86f56dd70d..d1d4a35561 100644 --- a/plugins/bitbucket-cloud-common/src/BitbucketCloudClient.ts +++ b/plugins/bitbucket-cloud-common/src/BitbucketCloudClient.ts @@ -40,6 +40,7 @@ export class BitbucketCloudClient { workspace: string, query: string, options?: FilterAndSortOptions & PartialResponseOptions, + pagelen?: number, ): WithPagination { const workspaceEnc = encodeURIComponent(workspace); return new WithPagination( @@ -50,6 +51,7 @@ export class BitbucketCloudClient { search_query: query, }), url => this.getTypeMapped(url), + pagelen, ); } diff --git a/plugins/bitbucket-cloud-common/src/pagination.test.ts b/plugins/bitbucket-cloud-common/src/pagination.test.ts index f263f51897..c355a798e4 100644 --- a/plugins/bitbucket-cloud-common/src/pagination.test.ts +++ b/plugins/bitbucket-cloud-common/src/pagination.test.ts @@ -24,7 +24,7 @@ interface TestResultItem { interface TestPage extends Models.Paginated {} describe('WithPagination', () => { - const createPagination = () => + const createPagination = (pagelen?: number) => new WithPagination( opts => new URL( @@ -45,6 +45,7 @@ describe('WithPagination', () => { ], }; }, + pagelen, ); it('iterateResults', async () => { @@ -121,4 +122,17 @@ describe('WithPagination', () => { ); }); }); + + it('uses custom pagelen when provided', async () => { + const pagination = createPagination(50); + + const page = await pagination.getPage(); + + expect(page.page).toEqual(1); + expect(page.next).toEqual('http://localhost/create-url?page=2&pagelen=50'); + expect(page.values).toHaveLength(1); + expect((page.values! as TestResultItem[])[0].url).toEqual( + 'http://localhost/create-url?page=1&pagelen=50', + ); + }); }); diff --git a/plugins/bitbucket-cloud-common/src/pagination.ts b/plugins/bitbucket-cloud-common/src/pagination.ts index 98e4f9f9d7..5d699a4a6f 100644 --- a/plugins/bitbucket-cloud-common/src/pagination.ts +++ b/plugins/bitbucket-cloud-common/src/pagination.ts @@ -30,10 +30,11 @@ export class WithPagination< constructor( private readonly createUrl: (options: PaginationOptions) => URL, private readonly fetch: (url: URL) => Promise, + private readonly pagelen?: number, ) {} getPage(options?: PaginationOptions): Promise { - const opts = { page: 1, pagelen: 100, ...options }; + const opts = { page: 1, pagelen: this.pagelen ?? 100, ...options }; const url = this.createUrl(opts); return this.fetch(url); } @@ -41,7 +42,7 @@ export class WithPagination< async *iteratePages( options?: PaginationOptions, ): AsyncGenerator { - const opts = { page: 1, pagelen: 100, ...options }; + const opts = { page: 1, pagelen: this.pagelen ?? 100, ...options }; let url: URL | undefined = this.createUrl(opts); let res; do { @@ -52,7 +53,7 @@ export class WithPagination< } async *iterateResults(options?: PaginationOptions) { - const opts = { page: 1, pagelen: 100, ...options }; + const opts = { page: 1, pagelen: this.pagelen ?? 100, ...options }; let url: URL | undefined = this.createUrl(opts); let res; do { diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md index 0836bee10d..a77ccf785c 100644 --- a/plugins/catalog-backend-module-aws/CHANGELOG.md +++ b/plugins/catalog-backend-module-aws/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-catalog-backend-module-aws +## 0.4.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-defaults@0.13.0-next.1 + - @backstage/integration@1.18.1-next.1 + - @backstage/integration-aws-node@0.1.18-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + - @backstage/plugin-kubernetes-common@0.9.7-next.1 + +## 0.4.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.0-next.0 + - @backstage/integration@1.18.1-next.0 + - @backstage/plugin-kubernetes-common@0.9.7-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.17 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.19.0 + ## 0.4.15 ### Patch Changes diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index 3f21de8eb1..12d6761143 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.15", + "version": "0.4.16-next.1", "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 842d0498cf..8f04f5cae5 100644 --- a/plugins/catalog-backend-module-azure/CHANGELOG.md +++ b/plugins/catalog-backend-module-azure/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-catalog-backend-module-azure +## 0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + +## 0.3.10-next.0 + +### Patch Changes + +- 84443f1: Adds config definitions for Azure Blob Storage. +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/config@1.3.3 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.19.0 + ## 0.3.9 ### Patch Changes diff --git a/plugins/catalog-backend-module-azure/config.d.ts b/plugins/catalog-backend-module-azure/config.d.ts index 50af529907..0eb488734f 100644 --- a/plugins/catalog-backend-module-azure/config.d.ts +++ b/plugins/catalog-backend-module-azure/config.d.ts @@ -55,6 +55,40 @@ export interface Config { schedule?: SchedulerServiceTaskScheduleDefinitionConfig; }; }; + /** + * AzureBlobEntityProvider configuration + */ + azureBlob?: + | { + [name: string]: { + /** + * (Required) The Azure Blob Storage container name. + */ + containerName: string; + /** + * (Required) The Azure Storage account name. + */ + accountName: string; + /** + * (Optional) TaskScheduleDefinition for the refresh. + */ + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; + }; + } + | { + /** + * (Required) The Azure Blob Storage container name. + */ + containerName: string; + /** + * (Required) The Azure Storage account name. + */ + accountName: string; + /** + * (Optional) TaskScheduleDefinition for the refresh. + */ + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; + }; }; }; } diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index 17c66699a5..44b8b9cfeb 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.9", + "version": "0.3.10-next.1", "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 5021408d44..a5b83b98da 100644 --- a/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-backstage-openapi +## 0.5.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/backend-openapi-utils@0.6.2-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + ## 0.5.6 ### Patch Changes diff --git a/plugins/catalog-backend-module-backstage-openapi/package.json b/plugins/catalog-backend-module-backstage-openapi/package.json index a17bf50b47..3243d6e317 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.6", + "version": "0.5.7-next.0", "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 45d3c5ed5b..bce5bc231b 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/plugin-catalog-backend-module-bitbucket-cloud +## 0.5.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + - @backstage/catalog-client@1.12.0 + +## 0.5.4-next.0 + +### Patch Changes + +- 2aded73: Allow for passing a `pagelen` parameter to configure the `pagelength` property of the `BitbucketCloudEntityProvider` `searchCode` pagination to resolve [bug](https://jira.atlassian.com/browse/BCLOUD-23644) pertaining to duplicate results being returned. +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.3-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-client@1.12.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.19.0 + - @backstage/plugin-events-node@0.4.15 + ## 0.5.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts b/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts index 1051976b24..bc6d618aa8 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts @@ -59,6 +59,11 @@ export interface Config { * (Optional) TaskScheduleDefinition for the discovery. */ schedule?: SchedulerServiceTaskScheduleDefinitionConfig; + /** + * (Optional) Number of results to fetch per page from Bitbucket API. Default to 100. + * @visibility frontend + */ + pagelen?: number; } | { [name: string]: { @@ -92,6 +97,11 @@ export interface Config { * (Optional) TaskScheduleDefinition for the discovery. */ schedule?: SchedulerServiceTaskScheduleDefinitionConfig; + /** + * (Optional) Number of results to fetch per page from Bitbucket API. Default to 100. + * @visibility frontend + */ + pagelen?: number; }; }; }; diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json index 68dfbaa619..14bef6edaf 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.3", + "version": "0.5.4-next.1", "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-cloud/src/providers/BitbucketCloudEntityProvider.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.ts index 64a58c2924..33327e1f33 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.ts @@ -409,7 +409,7 @@ export class BitbucketCloudEntityProvider implements EntityProvider { ].join(','); const searchResults = this.client - .searchCode(workspace, query, { fields }) + .searchCode(workspace, query, { fields }, this.config.pagelen) .iterateResults(); const result: IngestionTarget[] = []; diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProviderConfig.test.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProviderConfig.test.ts index 7f13616586..e039708099 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProviderConfig.test.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProviderConfig.test.ts @@ -92,6 +92,7 @@ describe('readProviderConfigs', () => { projectKey: undefined, repoSlug: undefined, }, + pagelen: undefined, }); expect(providerConfigs[1]).toEqual({ id: 'providerCustomCatalogPath', @@ -101,6 +102,7 @@ describe('readProviderConfigs', () => { projectKey: undefined, repoSlug: undefined, }, + pagelen: undefined, }); expect(providerConfigs[2]).toEqual({ id: 'providerWithProjectKeyFilter', @@ -110,6 +112,7 @@ describe('readProviderConfigs', () => { projectKey: /^projectKey.*filter$/, repoSlug: undefined, }, + pagelen: undefined, }); expect(providerConfigs[3]).toEqual({ id: 'providerWithRepoSlugFilter', @@ -119,6 +122,7 @@ describe('readProviderConfigs', () => { projectKey: undefined, repoSlug: /^repoSlug.*filter$/, }, + pagelen: undefined, }); expect(providerConfigs[4]).toEqual({ id: 'providerWithSchedule', @@ -134,6 +138,24 @@ describe('readProviderConfigs', () => { minutes: 3, }, }, + pagelen: undefined, }); }); + + it('provider config with pagelen', () => { + const config = new ConfigReader({ + catalog: { + providers: { + bitbucketCloud: { + workspace: 'test-ws', + pagelen: 50, + }, + }, + }, + }); + const providerConfigs = readProviderConfigs(config); + + expect(providerConfigs).toHaveLength(1); + expect(providerConfigs[0].pagelen).toEqual(50); + }); }); diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProviderConfig.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProviderConfig.ts index 5a806d2e0b..0a8259e4d1 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProviderConfig.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProviderConfig.ts @@ -32,6 +32,7 @@ export type BitbucketCloudEntityProviderConfig = { repoSlug?: RegExp; }; schedule?: SchedulerServiceTaskScheduleDefinition; + pagelen?: number; }; export function readProviderConfigs( @@ -72,6 +73,8 @@ function readProviderConfig( ) : undefined; + const pagelen = config.getOptionalNumber('pagelen'); + return { id, catalogPath, @@ -83,6 +86,7 @@ function readProviderConfig( repoSlug: repoSlugPattern ? compileRegExp(repoSlugPattern) : undefined, }, schedule, + pagelen, }; } diff --git a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md index 1df0cda05b..cb7767ce16 100644 --- a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-catalog-backend-module-bitbucket-server +## 0.5.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + - @backstage/catalog-client@1.12.0 + +## 0.5.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-client@1.12.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.19.0 + - @backstage/plugin-events-node@0.4.15 + ## 0.5.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-server/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json index ce3ec49ae8..f0bbf19128 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.3", + "version": "0.5.4-next.1", "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 4d06b61dbe..dfba428022 100644 --- a/plugins/catalog-backend-module-gcp/CHANGELOG.md +++ b/plugins/catalog-backend-module-gcp/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-catalog-backend-module-gcp +## 0.3.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-kubernetes-common@0.9.7-next.1 + +## 0.3.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.9.7-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/plugin-catalog-node@1.19.0 + ## 0.3.12 ### Patch Changes diff --git a/plugins/catalog-backend-module-gcp/package.json b/plugins/catalog-backend-module-gcp/package.json index 8fb1acd48f..7a313a6486 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.12", + "version": "0.3.13-next.1", "description": "A Backstage catalog backend module that helps integrate towards GCP", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-gerrit/CHANGELOG.md b/plugins/catalog-backend-module-gerrit/CHANGELOG.md index 097e4f344f..c1f9fe3867 100644 --- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md +++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-catalog-backend-module-gerrit +## 0.3.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + +## 0.3.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.19.0 + ## 0.3.6 ### Patch Changes diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index 1c4d7e8aa7..eec09488a6 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.6", + "version": "0.3.7-next.1", "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 3274f6c9f1..35bb7f8bd3 100644 --- a/plugins/catalog-backend-module-gitea/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitea/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-catalog-backend-module-gitea +## 0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + +## 0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.19.0 + ## 0.1.4 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitea/package.json b/plugins/catalog-backend-module-gitea/package.json index c9dacc7b64..601b973da0 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.4", + "version": "0.1.5-next.1", "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 6198a1942e..df63d6fb8f 100644 --- a/plugins/catalog-backend-module-github-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-github-org/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-catalog-backend-module-github-org +## 0.3.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-backend-module-github@0.11.1-next.1 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + +## 0.3.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-github@0.11.1-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/config@1.3.3 + - @backstage/plugin-catalog-node@1.19.0 + - @backstage/plugin-events-node@0.4.15 + ## 0.3.14 ### Patch Changes diff --git a/plugins/catalog-backend-module-github-org/package.json b/plugins/catalog-backend-module-github-org/package.json index f9c1f516fb..bab1bdfe00 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.14", + "version": "0.3.15-next.1", "description": "The github-org backend module for the catalog plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-github/CHANGELOG.md b/plugins/catalog-backend-module-github/CHANGELOG.md index 718a0035e8..2921c3960d 100644 --- a/plugins/catalog-backend-module-github/CHANGELOG.md +++ b/plugins/catalog-backend-module-github/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/plugin-catalog-backend-module-github +## 0.11.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-backend@3.1.1-next.1 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + - @backstage/catalog-client@1.12.0 + +## 0.11.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.1.1-next.0 + - @backstage/integration@1.18.1-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-client@1.12.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.19.0 + - @backstage/plugin-events-node@0.4.15 + ## 0.11.0 ### Minor Changes diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index 5187103233..7ed76c0ed7 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.0", + "version": "0.11.1-next.1", "description": "A Backstage catalog backend module that helps integrate towards GitHub", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md index 022eb1445a..b162bf1103 100644 --- a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-catalog-backend-module-gitlab-org +## 0.2.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-backend-module-gitlab@0.7.4-next.1 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + +## 0.2.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-gitlab@0.7.4-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/plugin-catalog-node@1.19.0 + - @backstage/plugin-events-node@0.4.15 + ## 0.2.13 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab-org/package.json b/plugins/catalog-backend-module-gitlab-org/package.json index 09f6ee7811..80a0b2627a 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.13", + "version": "0.2.14-next.1", "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 2610055302..18a1915ee6 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-catalog-backend-module-gitlab +## 0.7.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-defaults@0.13.0-next.1 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + +## 0.7.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.0-next.0 + - @backstage/integration@1.18.1-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.19.0 + - @backstage/plugin-events-node@0.4.15 + ## 0.7.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index c18be1028b..6a676975a2 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.3", + "version": "0.7.4-next.1", "description": "A Backstage catalog backend module that helps integrate towards GitLab", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts index 774da46553..06ad0b80db 100644 --- a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts @@ -33,6 +33,7 @@ const API_URL = `${SERVER_URL}/api/v4`; const PROJECTS_URL = `${API_URL}/projects`; const GROUP_PROJECTS_URL = `${API_URL}/groups/group%2Fsubgroup/projects`; const EXISTING_PROJECT_PATH = 'exist'; +const EXISTING_PROJECT_ID = 2; const PROJECT_LOCATION: LocationSpec = { type: 'gitlab-discovery', @@ -105,7 +106,7 @@ function setupFakeServer( ); }), rest.head( - `${API_URL}/projects/:project_path/repository/files/:file_path`, + `${API_URL}/projects/:projectIdentifier/repository/files/:file_path`, (req, res, ctx) => { if (req.headers.get('authorization') !== 'Bearer test-token') { return res( @@ -119,7 +120,11 @@ function setupFakeServer( return res(ctx.status(200)); } - if (EXISTING_PROJECT_PATH === req.params.project_path) { + if ( + // Gitlab GET project endpoint can use either numeric project.id or string project.path_with_namespace + EXISTING_PROJECT_ID.toString() === req.params.projectIdentifier || + EXISTING_PROJECT_PATH === req.params.projectIdentifier + ) { return res(ctx.status(200)); } @@ -385,7 +390,7 @@ describe('GitlabDiscoveryProcessor', () => { path_with_namespace: '1', }, { - id: 1, + id: EXISTING_PROJECT_ID, archived: false, default_branch: 'main', last_activity_at: '2021-08-05T11:03:05.774Z', diff --git a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts index 126bb07f03..5ae71dda06 100644 --- a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts +++ b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts @@ -139,7 +139,7 @@ export class GitLabDiscoveryProcessor implements CatalogProcessor { const project_branch = branch === '*' ? project.default_branch : branch; const projectHasFile: boolean = await client.hasFile( - project.path_with_namespace, + project.id, project_branch, catalogPath, ); diff --git a/plugins/catalog-backend-module-gitlab/src/__testUtils__/handlers.ts b/plugins/catalog-backend-module-gitlab/src/__testUtils__/handlers.ts index 9af00d2564..2b28381ceb 100644 --- a/plugins/catalog-backend-module-gitlab/src/__testUtils__/handlers.ts +++ b/plugins/catalog-backend-module-gitlab/src/__testUtils__/handlers.ts @@ -266,13 +266,13 @@ const httpProjectFindByIdDynamic = all_projects_response.map(project => { return res(ctx.json(all_projects_response.find(p => p.id === project.id))); }); }); -const httpProjectCatalogDynamic = all_projects_response.map(project => { - const path: string = project.path_with_namespace - ? project.path_with_namespace!.replace(/\//g, '%2F') - : `${project.path_with_namespace}%2F${project.name}`; +/** + * See https://docs.gitlab.com/api/repository_files/#get-file-from-repository + */ +const httpProjectCatalogDynamic = all_projects_response.flatMap(project => { return rest.head( - `${apiBaseUrl}/projects/${path}/repository/files/catalog-info.yaml`, + `${apiBaseUrl}/projects/${project.id.toString()}/repository/files/catalog-info.yaml`, (req, res, ctx) => { const branch = req.url.searchParams.get('ref'); if ( diff --git a/plugins/catalog-backend-module-gitlab/src/lib/client.test.ts b/plugins/catalog-backend-module-gitlab/src/lib/client.test.ts index 9b3bac6399..60974d2d0f 100644 --- a/plugins/catalog-backend-module-gitlab/src/lib/client.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/lib/client.test.ts @@ -581,20 +581,12 @@ describe('hasFile', () => { }); it('should find catalog file', async () => { - const hasFile = await client.hasFile( - 'group1/test-repo1', - 'main', - 'catalog-info.yaml', - ); + const hasFile = await client.hasFile(1, 'main', 'catalog-info.yaml'); expect(hasFile).toBe(true); }); it('should not find catalog file', async () => { - const hasFile = await client.hasFile( - 'group1/test-repo1', - 'unknown', - 'catalog-info.yaml', - ); + const hasFile = await client.hasFile(1, 'unknown', 'catalog-info.yaml'); expect(hasFile).toBe(false); }); }); diff --git a/plugins/catalog-backend-module-gitlab/src/lib/client.ts b/plugins/catalog-backend-module-gitlab/src/lib/client.ts index 3178e2c0d1..5c373dcf4a 100644 --- a/plugins/catalog-backend-module-gitlab/src/lib/client.ts +++ b/plugins/catalog-backend-module-gitlab/src/lib/client.ts @@ -342,18 +342,19 @@ export class GitLabClient { /** * General existence check. + * @see {@link https://docs.gitlab.com/api/repository_files/#get-file-from-repository | GitLab Repository Files API} * - * @param projectPath - The path to the project + * @param projectIdentifier - The identifier of the project, either the numeric ID or the namespaced path. * @param branch - The branch used to search * @param filePath - The path to the file */ async hasFile( - projectPath: string, + projectIdentifier: string | number, branch: string, filePath: string, ): Promise { const endpoint: string = `/projects/${encodeURIComponent( - projectPath, + projectIdentifier, )}/repository/files/${encodeURIComponent(filePath)}`; const request = new URL(`${this.config.apiBaseUrl}${endpoint}`); request.searchParams.append('ref', branch); diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts index 11b6a0ce04..b3371dcc9e 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts @@ -590,7 +590,7 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider { this.config.fallbackBranch; const hasFile = await client.hasFile( - project.path_with_namespace ?? '', + project.id, project_branch, this.config.catalogFile, ); diff --git a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md index aa22c5aad7..b15da28058 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md +++ b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/plugin-catalog-backend-module-incremental-ingestion +## 0.7.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-defaults@0.13.0-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-backend@3.1.1-next.1 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + +## 0.7.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.1.1-next.0 + - @backstage/backend-defaults@0.13.0-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.19.0 + - @backstage/plugin-events-node@0.4.15 + - @backstage/plugin-permission-common@0.9.1 + ## 0.7.4 ### Patch Changes diff --git a/plugins/catalog-backend-module-incremental-ingestion/package.json b/plugins/catalog-backend-module-incremental-ingestion/package.json index ffc675fcff..b63bfaa76b 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.4", + "version": "0.7.5-next.1", "description": "An entity provider for streaming large asset sources into the catalog", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md index 2a1dbf6d62..e3f5ef572d 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.11.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + ## 0.11.9 ### Patch Changes diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index 91ac03c93a..38845929f4 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.11.9", + "version": "0.11.10-next.0", "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 fbc0b1dfa7..0ee19e94f0 100644 --- a/plugins/catalog-backend-module-logs/CHANGELOG.md +++ b/plugins/catalog-backend-module-logs/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-catalog-backend-module-logs +## 0.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-backend@3.1.1-next.1 + - @backstage/plugin-events-node@0.4.16-next.0 + +## 0.1.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@3.1.1-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/plugin-events-node@0.4.15 + ## 0.1.14 ### Patch Changes diff --git a/plugins/catalog-backend-module-logs/package.json b/plugins/catalog-backend-module-logs/package.json index 3b27e9cd79..a51f0821ba 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.14", + "version": "0.1.15-next.1", "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 8ab315e9dd..3db73151fe 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-msgraph +## 0.8.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + ## 0.8.0 ### Minor Changes diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index f2f8a5d442..580fcc7962 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.0", + "version": "0.8.1-next.0", "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 6cef384c74..2c98a53a84 100644 --- a/plugins/catalog-backend-module-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-openapi/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-catalog-backend-module-openapi +## 0.2.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + +## 0.2.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-model@1.7.5 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.19.0 + ## 0.2.14 ### Patch Changes diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json index 155c24115d..96ced0dd97 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.14", + "version": "0.2.15-next.1", "description": "A Backstage catalog backend module that helps with OpenAPI specifications", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md index 14fa817eba..cdbee9bfb8 100644 --- a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md +++ b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-backend-module-puppetdb +## 0.2.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + ## 0.2.14 ### Patch Changes diff --git a/plugins/catalog-backend-module-puppetdb/package.json b/plugins/catalog-backend-module-puppetdb/package.json index ced5df4773..2501290bed 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.14", + "version": "0.2.15-next.0", "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 a654ff5c3d..b53acb9e97 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md +++ b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-catalog-backend-module-scaffolder-entity-model +## 0.2.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-scaffolder-common@1.7.2-next.1 + - @backstage/plugin-catalog-common@1.1.6-next.0 + +## 0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.7.2-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-model@1.7.5 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.19.0 + ## 0.2.12 ### 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 beed6b3028..f5d9a6c1af 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.12", + "version": "0.2.13-next.1", "description": "Adds support for the scaffolder specific entity model (e.g. the Template kind) to the catalog backend plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md index 1c4b8f8363..3df0e25d43 100644 --- a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md +++ b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend-module-unprocessed +## 0.6.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.10-next.0 + ## 0.6.4 ### Patch Changes diff --git a/plugins/catalog-backend-module-unprocessed/package.json b/plugins/catalog-backend-module-unprocessed/package.json index 32bcfe904f..d2f10d1aa7 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.4", + "version": "0.6.5-next.0", "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 54be12f4cb..7051f03873 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,75 @@ # @backstage/plugin-catalog-backend +## 3.1.2-next.2 + +### Patch Changes + +- 6493c98: Log before provider-orphaning eviction happens +- 77516c5: Added new `catalog:validate-entity` action to actions registry. + + This action can be used to validate entities against the software catalog. + This is useful for validating `catalog-info.yaml` file changes locally using the + Backstage MCP server. + +## 3.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-permission-node@0.10.5-next.0 + - @backstage/backend-openapi-utils@0.6.2-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + - @backstage/catalog-client@1.12.0 + +## 3.1.1-next.0 + +### Patch Changes + +- 9890488: Internal refactor to remove remnants of the old backend system +- 2aaf01a: Fix for duplicate search results in entity-facets API call +- e489661: Moved catalog processor and provider disabling and priorities under own config objects. + + This is due to issue with some existing providers, such as GitHub, using array syntax for the provider configuration. + + The new config format is not backwards compatible, so users will need to update their config files. The new format + is as follows: + + ```yaml + catalog: + providerOptions: + providerA: + disabled: false + providerB: + disabled: true + processorOptions: + processorA: + disabled: false + priority: 10 + processorB: + disabled: true + ``` + +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/plugin-permission-node@0.10.4 + - @backstage/backend-openapi-utils@0.6.1 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-client@1.12.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-catalog-node@1.19.0 + - @backstage/plugin-events-node@0.4.15 + - @backstage/plugin-permission-common@0.9.1 + ## 3.1.0 ### Minor Changes diff --git a/plugins/catalog-backend/config.d.ts b/plugins/catalog-backend/config.d.ts index f1b72e5b45..95d28d1eae 100644 --- a/plugins/catalog-backend/config.d.ts +++ b/plugins/catalog-backend/config.d.ts @@ -223,38 +223,36 @@ export interface Config { processingInterval?: HumanDuration | false; /** - * Catalog provide specific configuration. - * Additional configuration for providers are specified in the catalog - * modules. + * Provider-specific additional configuration options. */ - providers?: { + providerOptions?: { /** - * Name is the provider ID, e.g. "bitbucketServer" + * Key is the provider name, value is an object with additional configuration */ [name: string]: { /** - * Whether the provider is enabled or not. Defaults to true. + * Determines whether this provider is disabled or not. If not specified, + * defaults to false. */ - enabled?: boolean; + disabled?: boolean; }; }; + /** - * Configuration for entity processors. Additional configuration for - * processors are specified in the catalog modules. + * Processor-specific additional configuration options. */ - processors?: { + processorOptions?: { /** - * Name is the processor ID, e.g. "catalog-processor" + * Key is the processor name, value is an object with additional configuration */ [name: string]: { /** - * Whether the processor is enabled or not. Defaults to true. + * Determines whether this processor is disabled or not. If not specified, + * defaults to false. */ - enabled?: boolean; + disabled?: boolean; /** - * The priority of the processor, which is used to determine the order in which - * processors are run. The default priority is 20, and lower value means - * that the processor runs earlier. + * The default priority is 20, and lower value means that the processor runs earlier. */ priority?: number; }; diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index a880cc224f..d3ca09ead2 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "3.1.0", + "version": "3.1.2-next.2", "description": "The Backstage backend plugin that provides the Backstage catalog", "backstage": { "role": "backend-plugin", diff --git a/plugins/catalog-backend/src/actions/createValidateEntityAction.test.ts b/plugins/catalog-backend/src/actions/createValidateEntityAction.test.ts new file mode 100644 index 0000000000..19ec699cb7 --- /dev/null +++ b/plugins/catalog-backend/src/actions/createValidateEntityAction.test.ts @@ -0,0 +1,284 @@ +/* + * 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 { createValidateEntityAction } from './createValidateEntityAction'; +import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; +import { actionsRegistryServiceMock } from '@backstage/backend-test-utils/alpha'; +import { BackstageCredentials } from '@backstage/backend-plugin-api'; + +describe('createValidateEntityAction', () => { + const validEntityYaml = ` +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: test-component + namespace: default +spec: + type: service + lifecycle: production +`; + + const invalidYaml = ` +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: test-component + namespace: default +spec: + type: service + lifecycle: production + invalid: yaml: syntax: error +`; + + const validEntityObject = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test-component', + namespace: 'default', + }, + spec: { + type: 'service', + lifecycle: 'production', + }, + }; + + it('should validate a valid entity YAML and return success', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockCatalog = catalogServiceMock.mock(); + + // Mock validateEntity to return valid response + mockCatalog.validateEntity.mockResolvedValue({ + valid: true, + }); + + createValidateEntityAction({ + catalog: mockCatalog, + actionsRegistry: mockActionsRegistry, + }); + + const result = await mockActionsRegistry.invoke({ + id: 'test:validate-entity', + input: { entity: validEntityYaml }, + }); + + expect(result.output).toEqual({ + isValid: true, + isValidYaml: true, + errors: [], + entity: validEntityObject, + }); + + expect(mockCatalog.validateEntity).toHaveBeenCalledWith( + validEntityObject, + 'url:https://localhost/entity-validator', + { credentials: expect.any(Object) }, + ); + }); + + it('should validate entity with custom location', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockCatalog = catalogServiceMock.mock(); + + mockCatalog.validateEntity.mockResolvedValue({ + valid: true, + }); + + createValidateEntityAction({ + catalog: mockCatalog, + actionsRegistry: mockActionsRegistry, + }); + + const customLocation = 'https://github.com/example/repo/catalog-info.yaml'; + + await mockActionsRegistry.invoke({ + id: 'test:validate-entity', + input: { + entity: validEntityYaml, + location: customLocation, + }, + }); + + expect(mockCatalog.validateEntity).toHaveBeenCalledWith( + validEntityObject, + customLocation, + { credentials: expect.any(Object) }, + ); + }); + + it('should return validation errors when entity is invalid', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockCatalog = catalogServiceMock.mock(); + + const validationErrors = [ + { name: 'ValidationError', message: 'Missing required field: spec.type' }, + { name: 'ValidationError', message: 'Invalid lifecycle value' }, + ]; + + mockCatalog.validateEntity.mockResolvedValue({ + valid: false, + errors: validationErrors, + }); + + createValidateEntityAction({ + catalog: mockCatalog, + actionsRegistry: mockActionsRegistry, + }); + + const result = await mockActionsRegistry.invoke({ + id: 'test:validate-entity', + input: { entity: validEntityYaml }, + }); + + expect(result.output).toEqual({ + isValid: false, + isValidYaml: true, + errors: ['Missing required field: spec.type', 'Invalid lifecycle value'], + entity: undefined, + }); + }); + + it('should handle invalid YAML syntax', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockCatalog = catalogServiceMock.mock(); + + createValidateEntityAction({ + catalog: mockCatalog, + actionsRegistry: mockActionsRegistry, + }); + + const result = await mockActionsRegistry.invoke({ + id: 'test:validate-entity', + input: { entity: invalidYaml }, + }); + + expect(result.output).toEqual({ + isValid: false, + isValidYaml: false, + errors: [expect.stringContaining('YAML parsing error')], + }); + + // validateEntity should not be called if YAML parsing fails + expect(mockCatalog.validateEntity).not.toHaveBeenCalled(); + }); + + it('should handle catalog service errors gracefully', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockCatalog = catalogServiceMock.mock(); + + const catalogError = new Error('Catalog service unavailable'); + mockCatalog.validateEntity.mockRejectedValue(catalogError); + + createValidateEntityAction({ + catalog: mockCatalog, + actionsRegistry: mockActionsRegistry, + }); + + const result = await mockActionsRegistry.invoke({ + id: 'test:validate-entity', + input: { entity: validEntityYaml }, + }); + + expect(result.output).toEqual({ + isValid: false, + isValidYaml: false, + errors: ['Validation error: Catalog service unavailable'], + }); + }); + + it('should handle empty YAML input', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockCatalog = catalogServiceMock.mock(); + + createValidateEntityAction({ + catalog: mockCatalog, + actionsRegistry: mockActionsRegistry, + }); + + const result = await mockActionsRegistry.invoke({ + id: 'test:validate-entity', + input: { entity: '' }, + }); + + expect(result.output).toEqual({ + isValid: false, + isValidYaml: false, + errors: [expect.stringContaining('Validation error')], + }); + }); + + it('should handle malformed YAML with special characters', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockCatalog = catalogServiceMock.mock(); + + createValidateEntityAction({ + catalog: mockCatalog, + actionsRegistry: mockActionsRegistry, + }); + + const malformedYaml = ` +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: test-component + annotations: + invalid: "unclosed quote +spec: + type: service +`; + + const result = await mockActionsRegistry.invoke({ + id: 'test:validate-entity', + input: { entity: malformedYaml }, + }); + + expect(result.output).toEqual({ + isValid: false, + isValidYaml: false, + errors: [expect.stringContaining('YAML parsing error')], + }); + }); + + it('should pass credentials to catalog service', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + const mockCatalog = catalogServiceMock.mock(); + + mockCatalog.validateEntity.mockResolvedValue({ + valid: true, + }); + + createValidateEntityAction({ + catalog: mockCatalog, + actionsRegistry: mockActionsRegistry, + }); + + const mockCredentials: BackstageCredentials = { + $$type: '@backstage/BackstageCredentials', + principal: { type: 'user', userEntityRef: 'user:default/test' }, + }; + + await mockActionsRegistry.invoke({ + id: 'test:validate-entity', + input: { entity: validEntityYaml }, + credentials: mockCredentials, + }); + + expect(mockCatalog.validateEntity).toHaveBeenCalledWith( + validEntityObject, + 'url:https://localhost/entity-validator', + { credentials: mockCredentials }, + ); + }); +}); diff --git a/plugins/catalog-backend/src/actions/createValidateEntityAction.ts b/plugins/catalog-backend/src/actions/createValidateEntityAction.ts new file mode 100644 index 0000000000..a77a932e25 --- /dev/null +++ b/plugins/catalog-backend/src/actions/createValidateEntityAction.ts @@ -0,0 +1,99 @@ +/* + * 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 yaml from 'yaml'; +import { CatalogService } from '@backstage/plugin-catalog-node'; + +export const createValidateEntityAction = (options: { + actionsRegistry: ActionsRegistryService; + catalog: CatalogService; +}) => { + const { actionsRegistry, catalog } = options; + actionsRegistry.register({ + name: 'validate-entity', + title: 'Validate Catalog Entity', + description: ` + This action can be used to validate catalog-info.yaml file contents meant to be used with the software catalog. + It checks both that the YAML syntax is correct, and that the entity content is valid according to the catalog's rules. + `, + attributes: { + readOnly: true, + idempotent: true, + destructive: false, + }, + schema: { + input: z => + z.object({ + entity: z.string().describe('Entity YAML content to validate'), + location: z + .string() + .url() + .optional() + .describe('Location to validate'), + }), + output: z => + z.object({ + isValid: z.boolean().describe('Whether the entity is valid'), + isValidYaml: z.boolean().describe('Whether the YAML syntax is valid'), + errors: z.array(z.string()).describe('Array of validation errors'), + entity: z + .record(z.any()) + .optional() + .describe('Parsed entity object if valid'), + }), + }, + action: async ({ input, credentials }) => { + const { entity: content, location } = input; + try { + let entity; + try { + entity = yaml.parse(content); + } catch (yamlError) { + return { + output: { + isValid: false, + isValidYaml: false, + errors: [`YAML parsing error: ${yamlError.message}`], + }, + }; + } + + const resp = await catalog.validateEntity( + entity, + location ?? 'url:https://localhost/entity-validator', + { credentials }, + ); + + return { + output: { + isValid: resp.valid, + isValidYaml: true, + errors: resp.valid ? [] : resp.errors.map(e => e.message), + entity: resp.valid ? entity : undefined, + }, + }; + } catch (error) { + return { + output: { + isValid: false, + isValidYaml: false, + errors: [`Validation error: ${error.message}`], + }, + }; + } + }, + }); +}; diff --git a/plugins/catalog-backend/src/actions/index.ts b/plugins/catalog-backend/src/actions/index.ts new file mode 100644 index 0000000000..c53f956f62 --- /dev/null +++ b/plugins/catalog-backend/src/actions/index.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 { ActionsRegistryService } from '@backstage/backend-plugin-api/alpha'; +import { CatalogService } from '@backstage/plugin-catalog-node'; +import { createGetCatalogEntityAction } from './createGetCatalogEntityAction.ts'; +import { createValidateEntityAction } from './createValidateEntityAction.ts'; + +export const createCatalogActions = (options: { + actionsRegistry: ActionsRegistryService; + catalog: CatalogService; +}) => { + createGetCatalogEntityAction(options); + createValidateEntityAction(options); +}; diff --git a/plugins/catalog-backend/src/database/DefaultProcessingDatabase.test.ts b/plugins/catalog-backend/src/database/DefaultProcessingDatabase.test.ts index a09e938575..790762a428 100644 --- a/plugins/catalog-backend/src/database/DefaultProcessingDatabase.test.ts +++ b/plugins/catalog-backend/src/database/DefaultProcessingDatabase.test.ts @@ -58,6 +58,7 @@ describe('DefaultProcessingDatabase', () => { minSeconds: 100, maxSeconds: 150, }), + events: mockServices.events.mock(), }), }; } diff --git a/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts b/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts index 81c26c66ab..50c3cbb1c8 100644 --- a/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts +++ b/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts @@ -42,11 +42,7 @@ import { checkLocationKeyConflict } from './operations/refreshState/checkLocatio import { insertUnprocessedEntity } from './operations/refreshState/insertUnprocessedEntity'; import { updateUnprocessedEntity } from './operations/refreshState/updateUnprocessedEntity'; import { generateStableHash, generateTargetKey } from './util'; -import { - EventBroker, - EventParams, - EventsService, -} from '@backstage/plugin-events-node'; +import { EventParams, EventsService } from '@backstage/plugin-events-node'; import { DateTime } from 'luxon'; import { CATALOG_CONFLICTS_TOPIC } from '../constants'; import { CatalogConflictEventPayload } from '../catalog/types'; @@ -64,7 +60,7 @@ export class DefaultProcessingDatabase implements ProcessingDatabase { database: Knex; logger: LoggerService; refreshInterval: ProcessingIntervalFunction; - eventBroker?: EventBroker | EventsService; + events: EventsService; }, ) { initDatabaseMetrics(options.database); @@ -367,7 +363,7 @@ export class DefaultProcessingDatabase implements ProcessingDatabase { this.options.logger.warn( `Detected conflicting entityRef ${entityRef} already referenced by ${conflictingKey} and now also ${locationKey}`, ); - if (this.options.eventBroker && locationKey) { + if (locationKey) { const eventParams: EventParams = { topic: CATALOG_CONFLICTS_TOPIC, eventPayload: { @@ -378,7 +374,7 @@ export class DefaultProcessingDatabase implements ProcessingDatabase { lastConflictAt: DateTime.now().toISO()!, }, }; - await this.options.eventBroker?.publish(eventParams); + await this.options.events.publish(eventParams); } } } diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.test.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.test.ts index ab54a7b885..5e6312ec4a 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.test.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.test.ts @@ -70,6 +70,8 @@ describe('DefaultCatalogProcessingEngine', () => { orchestrator: orchestrator, stitcher: stitcher, createHash: () => hash, + scheduler: mockServices.scheduler(), + events: mockServices.events.mock(), }); db.transaction.mockImplementation(cb => cb((() => {}) as any)); @@ -136,7 +138,9 @@ describe('DefaultCatalogProcessingEngine', () => { knex: {} as any, orchestrator: orchestrator, stitcher: stitcher, + scheduler: mockServices.scheduler(), createHash: () => hash, + events: mockServices.events.mock(), }); db.transaction.mockImplementation(cb => cb((() => {}) as any)); @@ -219,7 +223,9 @@ describe('DefaultCatalogProcessingEngine', () => { knex: {} as any, orchestrator: orchestrator, stitcher: stitcher, + scheduler: mockServices.scheduler(), createHash: () => hash, + events: mockServices.events.mock(), }); db.transaction.mockImplementation(cb => cb((() => {}) as any)); @@ -296,7 +302,9 @@ describe('DefaultCatalogProcessingEngine', () => { knex: {} as any, orchestrator: orchestrator, stitcher: stitcher, + scheduler: mockServices.scheduler(), createHash: () => hash, + events: mockServices.events.mock(), }); db.transaction.mockImplementation(cb => cb((() => {}) as any)); @@ -355,8 +363,10 @@ describe('DefaultCatalogProcessingEngine', () => { knex: {} as any, orchestrator: orchestrator, stitcher: stitcher, + scheduler: mockServices.scheduler(), createHash: () => hash, pollingIntervalMs: 100, + events: mockServices.events.mock(), }); db.transaction.mockImplementation(cb => cb((() => {}) as any)); @@ -470,8 +480,10 @@ describe('DefaultCatalogProcessingEngine', () => { knex: {} as any, orchestrator: orchestrator, stitcher: stitcher, + scheduler: mockServices.scheduler(), createHash: () => hash, pollingIntervalMs: 100, + events: mockServices.events.mock(), }); db.transaction.mockImplementation(cb => cb((() => {}) as any)); @@ -575,8 +587,10 @@ describe('DefaultCatalogProcessingEngine', () => { knex: {} as any, orchestrator: orchestrator, stitcher: stitcher, + scheduler: mockServices.scheduler(), createHash: () => hash, pollingIntervalMs: 100, + events: mockServices.events.mock(), }); db.transaction.mockImplementation(cb => cb((() => {}) as any)); @@ -658,8 +672,10 @@ describe('DefaultCatalogProcessingEngine', () => { knex: {} as any, orchestrator: orchestrator, stitcher: stitcher, + scheduler: mockServices.scheduler(), createHash: () => hash, pollingIntervalMs: 100, + events: mockServices.events.mock(), }); db.transaction.mockImplementation(cb => cb((() => {}) as any)); @@ -746,8 +762,10 @@ describe('DefaultCatalogProcessingEngine', () => { knex: {} as any, orchestrator: orchestrator, stitcher: stitcher, + scheduler: mockServices.scheduler(), createHash: () => hash, pollingIntervalMs: 100, + events: mockServices.events.mock(), }); db.transaction.mockImplementation(cb => cb((() => {}) as any)); diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts index 266b80f9a4..b2a562ea6a 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts @@ -36,7 +36,7 @@ import { withActiveSpan, } from '../util/opentelemetry'; import { deleteOrphanedEntities } from '../database/operations/util/deleteOrphanedEntities'; -import { EventBroker, EventsService } from '@backstage/plugin-events-node'; +import { EventsService } from '@backstage/plugin-events-node'; import { CATALOG_ERRORS_TOPIC } from '../constants'; import { LoggerService, SchedulerService } from '@backstage/backend-plugin-api'; @@ -59,7 +59,7 @@ const stableStringifyArray = (arr: any[]) => { // is just one. export class DefaultCatalogProcessingEngine { private readonly config: Config; - private readonly scheduler?: SchedulerService; + private readonly scheduler: SchedulerService; private readonly logger: LoggerService; private readonly knex: Knex; private readonly processingDatabase: ProcessingDatabase; @@ -73,13 +73,13 @@ export class DefaultCatalogProcessingEngine { errors: Error[]; }) => Promise | void; private readonly tracker: ProgressTracker; - private readonly eventBroker?: EventBroker | EventsService; + private readonly events: EventsService; private stopFunc?: () => void; constructor(options: { config: Config; - scheduler?: SchedulerService; + scheduler: SchedulerService; logger: LoggerService; knex: Knex; processingDatabase: ProcessingDatabase; @@ -93,7 +93,7 @@ export class DefaultCatalogProcessingEngine { errors: Error[]; }) => Promise | void; tracker?: ProgressTracker; - eventBroker?: EventBroker | EventsService; + events: EventsService; }) { this.config = options.config; this.scheduler = options.scheduler; @@ -107,7 +107,7 @@ export class DefaultCatalogProcessingEngine { this.orphanCleanupIntervalMs = options.orphanCleanupIntervalMs ?? 30_000; this.onProcessingError = options.onProcessingError; this.tracker = options.tracker ?? progressTracker(); - this.eventBroker = options.eventBroker; + this.events = options.events; this.stopFunc = undefined; } @@ -201,7 +201,7 @@ export class DefaultCatalogProcessingEngine { const location = unprocessedEntity?.metadata?.annotations?.[ANNOTATION_LOCATION]; if (result.errors.length) { - this.eventBroker?.publish({ + this.events.publish({ topic: CATALOG_ERRORS_TOPIC, eventPayload: { entity: entityRef, @@ -370,25 +370,17 @@ export class DefaultCatalogProcessingEngine { } }; - if (this.scheduler) { - const abortController = new AbortController(); + const abortController = new AbortController(); + this.scheduler.scheduleTask({ + id: 'catalog_orphan_cleanup', + frequency: { milliseconds: this.orphanCleanupIntervalMs }, + timeout: { milliseconds: this.orphanCleanupIntervalMs * 0.8 }, + fn: runOnce, + signal: abortController.signal, + }); - this.scheduler.scheduleTask({ - id: 'catalog_orphan_cleanup', - frequency: { milliseconds: this.orphanCleanupIntervalMs }, - timeout: { milliseconds: this.orphanCleanupIntervalMs * 0.8 }, - fn: runOnce, - signal: abortController.signal, - }); - - return () => { - abortController.abort(); - }; - } - - const intervalKey = setInterval(runOnce, this.orphanCleanupIntervalMs); return () => { - clearInterval(intervalKey); + abortController.abort(); }; } } diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.test.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.test.ts index 31d7c34b94..beb3611206 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.test.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.test.ts @@ -97,7 +97,6 @@ describe('DefaultCatalogProcessingOrchestrator', () => { parser: defaultEntityDataParser, policy: EntityPolicies.allOf([]), rulesEnforcer: { isAllowed: () => true }, - legacySingleProcessorValidation: false, }); it('runs a minimal processing', async () => { @@ -192,7 +191,7 @@ describe('DefaultCatalogProcessingOrchestrator', () => { }); }); - it('runs all processor validations when asked to', async () => { + it('runs all processor validations', async () => { const validate = jest.fn(async () => true); const processor1: CatalogProcessor = { getProcessorName: () => 'processor1', @@ -213,7 +212,6 @@ describe('DefaultCatalogProcessingOrchestrator', () => { parser: defaultEntityDataParser, policy: EntityPolicies.allOf([]), rulesEnforcer: { isAllowed: () => true }, - legacySingleProcessorValidation: true, }); const modern = new DefaultCatalogProcessingOrchestrator({ @@ -226,13 +224,12 @@ describe('DefaultCatalogProcessingOrchestrator', () => { parser: defaultEntityDataParser, policy: EntityPolicies.allOf([]), rulesEnforcer: { isAllowed: () => true }, - legacySingleProcessorValidation: false, }); await expect(legacy.process({ entity })).resolves.toMatchObject({ ok: true, }); - expect(validate).toHaveBeenCalledTimes(1); + expect(validate).toHaveBeenCalledTimes(2); validate.mockClear(); @@ -291,7 +288,6 @@ describe('DefaultCatalogProcessingOrchestrator', () => { parser, policy: EntityPolicies.allOf([]), rulesEnforcer, - legacySingleProcessorValidation: false, }); rulesEnforcer.isAllowed.mockReturnValueOnce(true); @@ -333,7 +329,6 @@ describe('DefaultCatalogProcessingOrchestrator', () => { parser, policy: EntityPolicies.allOf([new FailingEntityPolicy()]), rulesEnforcer, - legacySingleProcessorValidation: false, }); await expect( diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts index b1fcba5d40..79873264a3 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts @@ -95,7 +95,6 @@ export class DefaultCatalogProcessingOrchestrator parser: CatalogProcessorParser; policy: EntityPolicy; rulesEnforcer: CatalogRulesEnforcer; - legacySingleProcessorValidation: boolean; }, ) {} @@ -309,9 +308,6 @@ export class DefaultCatalogProcessingOrchestrator ); if (thisValid) { valid = true; - if (this.options.legacySingleProcessorValidation) { - break; - } } } catch (e) { throw new InputError( diff --git a/plugins/catalog-backend/src/processing/evictEntitiesFromOrphanedProviders.test.ts b/plugins/catalog-backend/src/processing/evictEntitiesFromOrphanedProviders.test.ts index 1580194639..d28f39f5e1 100644 --- a/plugins/catalog-backend/src/processing/evictEntitiesFromOrphanedProviders.test.ts +++ b/plugins/catalog-backend/src/processing/evictEntitiesFromOrphanedProviders.test.ts @@ -13,62 +13,166 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { EntityProvider } from '@backstage/plugin-catalog-node'; -import { mockServices } from '@backstage/backend-test-utils'; + +import { mockServices, TestDatabases } from '@backstage/backend-test-utils'; import { DefaultProviderDatabase } from '../database/DefaultProviderDatabase'; -import { evictEntitiesFromOrphanedProviders } from './evictEntitiesFromOrphanedProviders'; +import { + evictEntitiesFromOrphanedProviders, + getOrphanedEntityProviderNames, +} from './evictEntitiesFromOrphanedProviders'; +import { applyDatabaseMigrations } from '../database/migrations'; +import { Knex } from 'knex'; -describe('evictEntitiesFromOrphanedProviders', () => { - const db = { - transaction: jest.fn().mockImplementation(cb => cb((() => {}) as any)), - replaceUnprocessedEntities: jest.fn(), - listReferenceSourceKeys: jest.fn(), - } as unknown as jest.Mocked; +jest.setTimeout(60_000); - const providers = [ - { getProviderName: () => 'provider1' }, - { getProviderName: () => 'provider2' }, - ] as unknown as EntityProvider[]; - const logger = mockServices.logger.mock(); +const databases = TestDatabases.create(); +const logger = mockServices.logger.mock(); - it('replaces unprocessed entities for orphaned providers with empty items', async () => { - db.listReferenceSourceKeys.mockResolvedValue(['foo', 'bar']); +afterEach(() => { + jest.resetAllMocks(); +}); - await evictEntitiesFromOrphanedProviders({ db, providers, logger }); +describe.each(databases.eachSupportedId())('%p', databaseId => { + let knex: Knex; + let db: DefaultProviderDatabase; - expect(db.replaceUnprocessedEntities).toHaveBeenCalledTimes(2); - expect(db.replaceUnprocessedEntities).toHaveBeenNthCalledWith( - 1, - expect.anything(), - { - sourceKey: 'foo', - type: 'full', - items: [], - }, - ); - expect(db.replaceUnprocessedEntities).toHaveBeenNthCalledWith( - 2, - expect.anything(), - { - sourceKey: 'bar', - type: 'full', - items: [], - }, - ); + beforeEach(async () => { + knex = await databases.init(databaseId); + await applyDatabaseMigrations(knex); + db = new DefaultProviderDatabase({ database: knex, logger }); }); - it('does not replace unprocessed entities for providers that are not orphaned', async () => { - db.listReferenceSourceKeys.mockResolvedValue(['foo', 'provider1']); + afterEach(async () => { + await knex.destroy(); + }); - await evictEntitiesFromOrphanedProviders({ db, providers, logger }); + describe('getOrphanedEntityProviderNames', () => { + it('correctly locates and logs orphaned providers', async () => { + const providers = [ + { getProviderName: () => 'provider1', connect: jest.fn() }, + { getProviderName: () => 'provider2', connect: jest.fn() }, + ]; - expect(db.replaceUnprocessedEntities).not.toHaveBeenCalledWith( - expect.anything(), - { - sourceKey: 'provider1', - type: 'full', - items: [], - }, - ); + await knex('refresh_state').insert([ + { + entity_id: 'x', + entity_ref: 'x', + unprocessed_entity: '{}', + processed_entity: '{}', + errors: '[]', + next_update_at: knex.fn.now(), + last_discovery_at: knex.fn.now(), + }, + ]); + await knex('refresh_state_references').insert([ + { source_key: 'provider2', target_entity_ref: 'x' }, + { source_key: 'provider3', target_entity_ref: 'x' }, + ]); + + await expect( + getOrphanedEntityProviderNames({ + db, + providers, + logger, + }), + ).resolves.toEqual(['provider3']); + + expect(logger.warn).toHaveBeenCalledTimes(4); + expect(logger.warn).toHaveBeenCalledWith( + `Found 1 orphaned entity provider(s)`, + ); + expect(logger.warn).toHaveBeenCalledWith( + `Database contained providers: 'provider2', 'provider3'`, + ); + expect(logger.warn).toHaveBeenCalledWith( + `Installed providers were: 'provider1', 'provider2'`, + ); + expect(logger.warn).toHaveBeenCalledWith( + `Orphaned providers were thus: 'provider3'`, + ); + }); + }); + + describe('evictEntitiesFromOrphanedProviders', () => { + it('replaces unprocessed entities for orphaned providers with empty items', async () => { + jest.spyOn(db, 'replaceUnprocessedEntities'); + + const providers = [ + { getProviderName: () => 'provider1', connect: jest.fn() }, + { getProviderName: () => 'provider2', connect: jest.fn() }, + ]; + + await knex('refresh_state').insert([ + { + entity_id: 'x', + entity_ref: 'x', + unprocessed_entity: '{}', + processed_entity: '{}', + errors: '[]', + next_update_at: knex.fn.now(), + last_discovery_at: knex.fn.now(), + }, + ]); + await knex('refresh_state_references').insert([ + { source_key: 'foo', target_entity_ref: 'x' }, + { source_key: 'bar', target_entity_ref: 'x' }, + ]); + + await evictEntitiesFromOrphanedProviders({ db, providers, logger }); + + expect(db.replaceUnprocessedEntities).toHaveBeenCalledTimes(2); + expect(db.replaceUnprocessedEntities).toHaveBeenCalledWith( + expect.anything(), + { + sourceKey: 'foo', + type: 'full', + items: [], + }, + ); + expect(db.replaceUnprocessedEntities).toHaveBeenCalledWith( + expect.anything(), + { + sourceKey: 'bar', + type: 'full', + items: [], + }, + ); + }); + + it('does not replace unprocessed entities for providers that are not orphaned', async () => { + jest.spyOn(db, 'replaceUnprocessedEntities'); + + const providers = [ + { getProviderName: () => 'provider1', connect: jest.fn() }, + { getProviderName: () => 'provider2', connect: jest.fn() }, + ]; + + await knex('refresh_state').insert([ + { + entity_id: 'x', + entity_ref: 'x', + unprocessed_entity: '{}', + processed_entity: '{}', + errors: '[]', + next_update_at: knex.fn.now(), + last_discovery_at: knex.fn.now(), + }, + ]); + await knex('refresh_state_references').insert([ + { source_key: 'foo', target_entity_ref: 'x' }, + { source_key: 'provider1', target_entity_ref: 'x' }, + ]); + + await evictEntitiesFromOrphanedProviders({ db, providers, logger }); + + expect(db.replaceUnprocessedEntities).not.toHaveBeenCalledWith( + expect.anything(), + { + sourceKey: 'provider1', + type: 'full', + items: [], + }, + ); + }); }); }); diff --git a/plugins/catalog-backend/src/processing/evictEntitiesFromOrphanedProviders.ts b/plugins/catalog-backend/src/processing/evictEntitiesFromOrphanedProviders.ts index a6bda5e8c5..753a266250 100644 --- a/plugins/catalog-backend/src/processing/evictEntitiesFromOrphanedProviders.ts +++ b/plugins/catalog-backend/src/processing/evictEntitiesFromOrphanedProviders.ts @@ -18,12 +18,14 @@ import { EntityProvider } from '@backstage/plugin-catalog-node'; import { LoggerService } from '@backstage/backend-plugin-api'; import { ProviderDatabase } from '../database/types'; -async function getOrphanedEntityProviderNames({ +export async function getOrphanedEntityProviderNames({ db, providers, + logger, }: { db: ProviderDatabase; providers: EntityProvider[]; + logger: LoggerService; }): Promise { const dbProviderNames = await db.transaction(async tx => db.listReferenceSourceKeys(tx), @@ -31,9 +33,21 @@ async function getOrphanedEntityProviderNames({ const providerNames = providers.map(p => p.getProviderName()); - return dbProviderNames.filter( + const orphaned = dbProviderNames.filter( dbProviderName => !providerNames.includes(dbProviderName), ); + + if (orphaned.length) { + const dbProviderNamesString = dbProviderNames.map(p => `'${p}'`).join(', '); + const providerNamesString = providerNames.map(p => `'${p}'`).join(', '); + const orphanedString = orphaned.map(p => `'${p}'`).join(', '); + logger.warn(`Found ${orphaned.length} orphaned entity provider(s)`); + logger.warn(`Database contained providers: ${dbProviderNamesString}`); + logger.warn(`Installed providers were: ${providerNamesString}`); + logger.warn(`Orphaned providers were thus: ${orphanedString}`); + } + + return orphaned; } async function removeEntitiesForProvider({ diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index dcd677152e..23178238c7 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -55,7 +55,7 @@ import { PlaceholderResolver, ScmLocationAnalyzer, } from '@backstage/plugin-catalog-node'; -import { EventBroker, EventsService } from '@backstage/plugin-events-node'; +import { EventsService } from '@backstage/plugin-events-node'; import { Permission, PermissionAuthorizer, @@ -111,6 +111,7 @@ import { catalogEntityPermissionResourceRef, CatalogPermissionRuleInput, } from '@backstage/plugin-catalog-node/alpha'; +import { filterAndSortProcessors, filterProviders } from './util'; export type CatalogEnvironment = { logger: LoggerService; @@ -119,10 +120,11 @@ export type CatalogEnvironment = { reader: UrlReaderService; permissions: PermissionsService | PermissionAuthorizer; permissionsRegistry?: PermissionsRegistryService; - scheduler?: SchedulerService; + scheduler: SchedulerService; auth: AuthService; httpAuth: HttpAuthService; - auditor?: AuditorService; + auditor: AuditorService; + events: EventsService; }; /** @@ -168,8 +170,6 @@ export class CatalogBuilder { private readonly permissions: Permission[]; private readonly permissionRules: CatalogPermissionRuleInput[]; private allowedLocationType: string[]; - private legacySingleProcessorValidation = false; - private eventBroker?: EventBroker | EventsService; /** * Creates a catalog builder. @@ -216,31 +216,6 @@ export class CatalogBuilder { return this; } - /** - * Processing interval determines how often entities should be processed. - * Seconds provided will be multiplied by 1.5 - * The default processing interval is 100-150 seconds. - * setting this too low will potentially deplete request quotas to upstream services. - */ - setProcessingIntervalSeconds(seconds: number): CatalogBuilder { - this.processingInterval = createRandomProcessingInterval({ - minSeconds: seconds, - maxSeconds: seconds * 1.5, - }); - return this; - } - - /** - * Overwrites the default processing interval function used to spread - * entity updates in the catalog. - */ - setProcessingInterval( - processingInterval: ProcessingIntervalFunction, - ): CatalogBuilder { - this.processingInterval = processingInterval; - return this; - } - /** * Overwrites the default location analyzer. */ @@ -427,23 +402,6 @@ export class CatalogBuilder { return this; } - /** - * Enables the legacy behaviour of canceling validation early whenever only a - * single processor declares an entity kind to be valid. - */ - useLegacySingleProcessorValidation(): this { - this.legacySingleProcessorValidation = true; - return this; - } - - /** - * Enables the publishing of events for conflicts in the DefaultProcessingDatabase - */ - setEventBroker(broker: EventBroker | EventsService): CatalogBuilder { - this.eventBroker = broker; - return this; - } - /** * Wires up and returns all of the component parts of the catalog */ @@ -461,6 +419,7 @@ export class CatalogBuilder { auditor, auth, httpAuth, + events, } = this.env; const enableRelationsCompatibility = Boolean( @@ -485,8 +444,8 @@ export class CatalogBuilder { const processingDatabase = new DefaultProcessingDatabase({ database: dbClient, logger, + events, refreshInterval: this.processingInterval, - eventBroker: this.eventBroker, }); const providerDatabase = new DefaultProviderDatabase({ database: dbClient, @@ -523,7 +482,6 @@ export class CatalogBuilder { logger, parser, policy, - legacySingleProcessorValidation: this.legacySingleProcessorValidation, }); const entitiesCatalog = new AuthorizedEntitiesCatalog( @@ -568,11 +526,12 @@ export class CatalogBuilder { const locationStore = new DefaultLocationStore(dbClient); const configLocationProvider = new ConfigLocationEntityProvider(config); - const entityProviders = this.filterProviders( + const entityProviders = filterProviders( lodash.uniqBy( [...this.entityProviders, locationStore, configLocationProvider], provider => provider.getProviderName(), ), + config, ); const processingEngine = new DefaultCatalogProcessingEngine({ @@ -588,7 +547,7 @@ export class CatalogBuilder { onProcessingError: event => { this.onProcessingError?.(event); }, - eventBroker: this.eventBroker, + events, }); const locationAnalyzer = @@ -727,52 +686,7 @@ export class CatalogBuilder { this.checkMissingExternalProcessors(processors); - const filteredProcessors = this.filterProcessors(processors); - - // Lastly sort the processors by priority. Config can override the - // priority of a processor to allow control of 3rd party processors. - filteredProcessors.sort((a, b) => { - const getProcessorPriority = (processor: CatalogProcessor) => { - try { - return ( - config.getOptionalNumber( - `catalog.processors.${processor.getProcessorName()}.priority`, - ) ?? - processor.getPriority?.() ?? - 20 - ); - } catch (_) { - // In case the processor config is not an object, just return default priority - return 20; - } - }; - - const aPriority = getProcessorPriority(a); - const bPriority = getProcessorPriority(b); - return aPriority - bPriority; - }); - - return filteredProcessors; - } - - private filterProcessors(processors: CatalogProcessor[]) { - const { config } = this.env; - const processorsConfig = config.getOptionalConfig('catalog.processors'); - if (!processorsConfig) { - return processors; - } - - return processors.filter(p => { - try { - const processorConfig = processorsConfig.getOptionalConfig( - p.getProcessorName(), - ); - return processorConfig?.getOptionalBoolean('enabled') ?? true; - } catch (_) { - // In case the processor config is not an object, just include the processor - return true; - } - }); + return filterAndSortProcessors(processors, config); } // TODO(Rugvip): These old processors are removed, for a while we'll be throwing @@ -885,26 +799,6 @@ export class CatalogBuilder { ); } - private filterProviders(providers: EntityProvider[]) { - const { config } = this.env; - const providersConfig = config.getOptionalConfig('catalog.providers'); - if (!providersConfig) { - return providers; - } - - return providers.filter(p => { - try { - const providerConfig = providersConfig.getOptionalConfig( - p.getProviderName(), - ); - return providerConfig?.getOptionalBoolean('enabled') ?? true; - } catch (_) { - // In case the provider config is not an object, just include the provider - return true; - } - }); - } - private static getDefaultProcessingInterval( config: Config, ): ProcessingIntervalFunction { diff --git a/plugins/catalog-backend/src/service/CatalogPlugin.ts b/plugins/catalog-backend/src/service/CatalogPlugin.ts index a9bb2c9285..cfe05712d9 100644 --- a/plugins/catalog-backend/src/service/CatalogPlugin.ts +++ b/plugins/catalog-backend/src/service/CatalogPlugin.ts @@ -45,7 +45,7 @@ import { Permission } from '@backstage/plugin-permission-common'; import { merge } from 'lodash'; import { CatalogBuilder } from './CatalogBuilder'; import { actionsRegistryServiceRef } from '@backstage/backend-plugin-api/alpha'; -import { createGetCatalogEntityAction } from '../actions/createGetCatalogEntityAction'; +import { createCatalogActions } from '../actions'; class CatalogLocationsExtensionPointImpl implements CatalogLocationsExtensionPoint @@ -271,10 +271,9 @@ export const catalogPlugin = createBackendPlugin({ auth, httpAuth, auditor, + events, }); - builder.setEventBroker(events); - if (processingExtensions.onProcessingErrorHandler) { builder.subscribe({ onProcessingError: processingExtensions.onProcessingErrorHandler, @@ -321,7 +320,7 @@ export const catalogPlugin = createBackendPlugin({ httpRouter.use(router); - createGetCatalogEntityAction({ + createCatalogActions({ catalog, actionsRegistry, }); diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts index 0b8dec71bb..e9d00f3451 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts @@ -2359,6 +2359,52 @@ describe('DefaultEntitiesCatalog', () => { }); }, ); + + it.each(databases.eachSupportedId())( + 'works when the entity is duplicated in search results, %p', + async databaseId => { + await createDatabase(databaseId); + + await addEntityToSearch({ + apiVersion: 'a', + kind: 'k', + metadata: { + name: 'one', + uid: 'uid-a', + }, + spec: {}, + }); + + // Manually insert a duplicate search entry, this shouldn't happen but does in reality + await knex('search').insert([ + { + entity_id: 'uid-a', + key: 'metadata.name', + value: 'one', + original_value: 'one', + }, + ]); + + const catalog = new DefaultEntitiesCatalog({ + database: knex, + logger: mockServices.logger.mock(), + stitcher, + }); + + await expect( + catalog.facets({ + facets: ['metadata.name'], + credentials: mockCredentials.none(), + }), + ).resolves.toEqual({ + facets: { + 'metadata.name': expect.arrayContaining([ + { value: 'one', count: 1 }, + ]), + }, + }); + }, + ); }); }); diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts index 33847ea87b..92c0bb0ecf 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts @@ -683,7 +683,7 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog { .select({ facet: 'search.key', value: 'search.original_value', - count: this.database.raw('count(*)'), + count: this.database.raw('count(DISTINCT search.entity_id)'), }) .groupBy(['search.key', 'search.original_value']); diff --git a/plugins/catalog-backend/src/service/DefaultRefreshService.test.ts b/plugins/catalog-backend/src/service/DefaultRefreshService.test.ts index a89b21f79f..29519e9e6b 100644 --- a/plugins/catalog-backend/src/service/DefaultRefreshService.test.ts +++ b/plugins/catalog-backend/src/service/DefaultRefreshService.test.ts @@ -57,6 +57,7 @@ describe('DefaultRefreshService', () => { database: knex, logger, refreshInterval: () => 100, + events: mockServices.events.mock(), }), catalogDb: new DefaultCatalogDatabase({ database: knex, @@ -121,6 +122,7 @@ describe('DefaultRefreshService', () => { processingDatabase: db, knex: knex, stitcher: stitcher, + scheduler: mockServices.scheduler(), orchestrator: { async process(request: EntityProcessingRequest) { const entityRef = stringifyEntityRef(request.entity); @@ -161,6 +163,7 @@ describe('DefaultRefreshService', () => { }, createHash: () => createHash('sha1'), pollingIntervalMs: 50, + events: mockServices.events.mock(), }); return engine; diff --git a/plugins/catalog-backend/src/service/createRouter.test.ts b/plugins/catalog-backend/src/service/createRouter.test.ts index 0246a3d56b..299395552a 100644 --- a/plugins/catalog-backend/src/service/createRouter.test.ts +++ b/plugins/catalog-backend/src/service/createRouter.test.ts @@ -156,12 +156,12 @@ describe('createRouter readonly disabled', () => { logger: mockServices.logger.mock(), refreshService, config: new ConfigReader(undefined), - permissionIntegrationRouter: express.Router(), auth: mockServices.auth(), httpAuth: mockServices.httpAuth(), locationAnalyzer, permissionsService, enableRelationsCompatibility: true, // added + auditor: mockServices.auditor.mock(), }); app = await wrapServer(express().use(router)); @@ -218,12 +218,12 @@ describe('createRouter readonly disabled', () => { logger: mockServices.logger.mock(), refreshService, config: new ConfigReader(undefined), - permissionIntegrationRouter: express.Router(), auth: mockServices.auth(), httpAuth: mockServices.httpAuth(), locationAnalyzer, permissionsService, enableRelationsCompatibility: true, + auditor: mockServices.auditor.mock(), }); app = await wrapServer(express().use(router)); entitiesCatalog.entities.mockResolvedValueOnce({ @@ -951,6 +951,7 @@ describe('createRouter readonly and raw json enabled', () => { permissionIntegrationRouter: express.Router(), auth: mockServices.auth(), httpAuth: mockServices.httpAuth(), + orchestrator: { process: jest.fn() }, permissionsService, auditor: mockServices.auditor.mock(), }); @@ -1171,6 +1172,7 @@ describe('NextRouter permissioning', () => { }), auth: mockServices.auth(), httpAuth: mockServices.httpAuth(), + orchestrator: { process: jest.fn() }, permissionsService, auditor: mockServices.auditor.mock(), }); diff --git a/plugins/catalog-backend/src/service/createRouter.ts b/plugins/catalog-backend/src/service/createRouter.ts index 9c9b97eb4c..dc7678a3b9 100644 --- a/plugins/catalog-backend/src/service/createRouter.ts +++ b/plugins/catalog-backend/src/service/createRouter.ts @@ -20,7 +20,6 @@ import { HttpAuthService, LoggerService, PermissionsService, - SchedulerService, } from '@backstage/backend-plugin-api'; import { ANNOTATION_LOCATION, @@ -70,17 +69,15 @@ export interface RouterOptions { entitiesCatalog?: EntitiesCatalog; locationAnalyzer?: LocationAnalyzer; locationService: LocationService; - orchestrator?: CatalogProcessingOrchestrator; + orchestrator: CatalogProcessingOrchestrator; refreshService?: RefreshService; - scheduler?: SchedulerService; logger: LoggerService; config: Config; permissionIntegrationRouter?: express.Router; auth: AuthService; httpAuth: HttpAuthService; permissionsService: PermissionsService; - // TODO: Require AuditorService once `backend-legacy` is removed - auditor?: AuditorService; + auditor: AuditorService; enableRelationsCompatibility?: boolean; } @@ -124,7 +121,7 @@ export async function createRouter( router.post('/refresh', async (req, res) => { const { authorizationToken, ...restBody } = req.body; - const auditorEvent = await auditor?.createEvent({ + const auditorEvent = await auditor.createEvent({ eventId: 'entity-mutate', severityLevel: 'medium', meta: { @@ -160,7 +157,7 @@ export async function createRouter( if (entitiesCatalog) { router .get('/entities', async (req, res) => { - const auditorEvent = await auditor?.createEvent({ + const auditorEvent = await auditor.createEvent({ eventId: 'entity-fetch', request: req, meta: { @@ -258,7 +255,7 @@ export async function createRouter( } }) .get('/entities/by-query', async (req, res) => { - const auditorEvent = await auditor?.createEvent({ + const auditorEvent = await auditor.createEvent({ eventId: 'entity-fetch', request: req, meta: { @@ -311,7 +308,7 @@ export async function createRouter( .get('/entities/by-uid/:uid', async (req, res) => { const { uid } = req.params; - const auditorEvent = await auditor?.createEvent({ + const auditorEvent = await auditor.createEvent({ eventId: 'entity-fetch', request: req, meta: { @@ -356,7 +353,7 @@ export async function createRouter( .delete('/entities/by-uid/:uid', async (req, res) => { const { uid } = req.params; - const auditorEvent = await auditor?.createEvent({ + const auditorEvent = await auditor.createEvent({ eventId: 'entity-mutate', severityLevel: 'medium', request: req, @@ -385,7 +382,7 @@ export async function createRouter( const { kind, namespace, name } = req.params; const entityRef = stringifyEntityRef({ kind, namespace, name }); - const auditorEvent = await auditor?.createEvent({ + const auditorEvent = await auditor.createEvent({ eventId: 'entity-fetch', request: req, meta: { @@ -420,7 +417,7 @@ export async function createRouter( const { kind, namespace, name } = req.params; const entityRef = stringifyEntityRef({ kind, namespace, name }); - const auditorEvent = await auditor?.createEvent({ + const auditorEvent = await auditor.createEvent({ eventId: 'entity-fetch', request: req, meta: { @@ -456,7 +453,7 @@ export async function createRouter( }, ) .post('/entities/by-refs', async (req, res) => { - const auditorEvent = await auditor?.createEvent({ + const auditorEvent = await auditor.createEvent({ eventId: 'entity-fetch', request: req, meta: { @@ -495,7 +492,7 @@ export async function createRouter( } }) .get('/entity-facets', async (req, res) => { - const auditorEvent = await auditor?.createEvent({ + const auditorEvent = await auditor.createEvent({ eventId: 'entity-facets', request: req, }); @@ -525,7 +522,7 @@ export async function createRouter( const location = await validateRequestBody(req, locationInput); const dryRun = yn(req.query.dryRun, { default: false }); - const auditorEvent = await auditor?.createEvent({ + const auditorEvent = await auditor.createEvent({ eventId: 'location-mutate', severityLevel: dryRun ? 'low' : 'medium', request: req, @@ -570,7 +567,7 @@ export async function createRouter( } }) .get('/locations', async (req, res) => { - const auditorEvent = await auditor?.createEvent({ + const auditorEvent = await auditor.createEvent({ eventId: 'location-fetch', request: req, meta: { @@ -597,7 +594,7 @@ export async function createRouter( .get('/locations/:id', async (req, res) => { const { id } = req.params; - const auditorEvent = await auditor?.createEvent({ + const auditorEvent = await auditor.createEvent({ eventId: 'location-fetch', request: req, meta: { @@ -628,7 +625,7 @@ export async function createRouter( .delete('/locations/:id', async (req, res) => { const { id } = req.params; - const auditorEvent = await auditor?.createEvent({ + const auditorEvent = await auditor.createEvent({ eventId: 'location-mutate', severityLevel: 'medium', request: req, @@ -659,7 +656,7 @@ export async function createRouter( const { kind, namespace, name } = req.params; const locationRef = `${kind}:${namespace}/${name}`; - const auditorEvent = await auditor?.createEvent({ + const auditorEvent = await auditor.createEvent({ eventId: 'location-fetch', request: req, meta: { @@ -692,7 +689,7 @@ export async function createRouter( if (locationAnalyzer) { router.post('/analyze-location', async (req, res) => { - const auditorEvent = await auditor?.createEvent({ + const auditorEvent = await auditor.createEvent({ eventId: 'location-analyze', request: req, }); @@ -743,86 +740,84 @@ export async function createRouter( }); } - if (orchestrator) { - router.post('/validate-entity', async (req, res) => { - const auditorEvent = await auditor?.createEvent({ - eventId: 'entity-validate', - request: req, + router.post('/validate-entity', async (req, res) => { + const auditorEvent = await auditor.createEvent({ + eventId: 'entity-validate', + request: req, + }); + + try { + const bodySchema = z.object({ + entity: z.unknown(), + location: z.string(), }); + let body: z.infer; + let entity: Entity; + let location: { type: string; target: string }; try { - const bodySchema = z.object({ - entity: z.unknown(), - location: z.string(), - }); - - let body: z.infer; - let entity: Entity; - let location: { type: string; target: string }; - try { - body = await validateRequestBody(req, bodySchema); - entity = validateEntityEnvelope(body.entity); - location = parseLocationRef(body.location); - if (location.type !== 'url') - throw new TypeError( - `Invalid location ref ${body.location}, only 'url:' is supported, e.g. url:https://host/path`, - ); - } catch (err) { - await auditorEvent?.fail({ - error: err, - }); - - return res.status(400).json({ - errors: [serializeError(err)], - }); - } - - const credentials = await httpAuth.credentials(req); - const authorizedValidationService = new AuthorizedValidationService( - orchestrator, - permissionsService, - ); - const processingResult = await authorizedValidationService.process( - { - entity: { - ...entity, - metadata: { - ...entity.metadata, - annotations: { - [ANNOTATION_LOCATION]: body.location, - [ANNOTATION_ORIGIN_LOCATION]: body.location, - ...entity.metadata.annotations, - }, - }, - }, - }, - credentials, - ); - - if (!processingResult.ok) { - const errors = processingResult.errors.map(e => serializeError(e)); - - await auditorEvent?.fail({ - // TODO(Rugvip): Seems like there aren't proper types for AggregateError yet - error: (AggregateError as any)(errors, 'Could not validate entity'), - }); - - res.status(400).json({ - errors, - }); - } - - await auditorEvent?.success(); - - return res.status(200).end(); + body = await validateRequestBody(req, bodySchema); + entity = validateEntityEnvelope(body.entity); + location = parseLocationRef(body.location); + if (location.type !== 'url') + throw new TypeError( + `Invalid location ref ${body.location}, only 'url:' is supported, e.g. url:https://host/path`, + ); } catch (err) { await auditorEvent?.fail({ error: err, }); - throw err; + + return res.status(400).json({ + errors: [serializeError(err)], + }); } - }); - } + + const credentials = await httpAuth.credentials(req); + const authorizedValidationService = new AuthorizedValidationService( + orchestrator, + permissionsService, + ); + const processingResult = await authorizedValidationService.process( + { + entity: { + ...entity, + metadata: { + ...entity.metadata, + annotations: { + [ANNOTATION_LOCATION]: body.location, + [ANNOTATION_ORIGIN_LOCATION]: body.location, + ...entity.metadata.annotations, + }, + }, + }, + }, + credentials, + ); + + if (!processingResult.ok) { + const errors = processingResult.errors.map(e => serializeError(e)); + + await auditorEvent?.fail({ + // TODO(Rugvip): Seems like there aren't proper types for AggregateError yet + error: (AggregateError as any)(errors, 'Could not validate entity'), + }); + + res.status(400).json({ + errors, + }); + } + + await auditorEvent?.success(); + + return res.status(200).end(); + } catch (err) { + await auditorEvent?.fail({ + error: err, + }); + throw err; + } + }); return router; } diff --git a/plugins/catalog-backend/src/service/util.test.ts b/plugins/catalog-backend/src/service/util.test.ts new file mode 100644 index 0000000000..9a0d2cc799 --- /dev/null +++ b/plugins/catalog-backend/src/service/util.test.ts @@ -0,0 +1,195 @@ +/* + * 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 } from '@backstage/backend-test-utils'; +import { filterAndSortProcessors, filterProviders } from './util'; + +describe('filterAndSortProcessors', () => { + it('should filter processors', () => { + const p1 = { getProcessorName: () => 'processor1' }; + const p2 = { getProcessorName: () => 'processor2' }; + + expect( + filterAndSortProcessors([p1, p2], mockServices.rootConfig({ data: {} })), + ).toEqual([p1, p2]); + + expect( + filterAndSortProcessors( + [p1, p2], + mockServices.rootConfig({ + data: { + catalog: { + processorOptions: { + processor1: { disabled: true }, + }, + }, + }, + }), + ), + ).toEqual([p2]); + + expect( + filterAndSortProcessors( + [p1, p2], + mockServices.rootConfig({ + data: { + catalog: { + processorOptions: { + processor2: { disabled: true }, + }, + }, + }, + }), + ), + ).toEqual([p1]); + }); + + it('should sort processors', () => { + const p1 = { getProcessorName: () => 'processor1', getPriority: () => 10 }; + const p2 = { getProcessorName: () => 'processor2' }; + const p3 = { + getProcessorName: () => 'processor3', + getPriority: () => { + throw new Error('failed'); + }, + }; + + expect( + filterAndSortProcessors( + [p1, p2, p3], + mockServices.rootConfig({ data: {} }), + ), + ).toEqual([p1, p2, p3]); // p2 and p3 got the defaults + + expect( + filterAndSortProcessors( + [p1, p2, p3], + mockServices.rootConfig({ + data: { + catalog: { + processorOptions: { + processor2: { priority: 2 }, + processor3: { priority: 1 }, + }, + }, + }, + }), + ), + ).toEqual([p3, p2, p1]); + }); + + it('rejects invalid config', () => { + const p1 = { getProcessorName: () => 'processor1' }; + const p2 = { getProcessorName: () => 'processor1' }; + + expect(() => + filterAndSortProcessors( + [p1, p2], + mockServices.rootConfig({ + data: { + catalog: { + processorOptions: { + processor1: { disabled: 'i guess so, maybe' }, + }, + }, + }, + }), + ), + ).toThrowErrorMatchingInlineSnapshot( + `"Unable to convert config value for key 'catalog.processorOptions.processor1.disabled' in 'mock-config' to a boolean"`, + ); + + expect(() => + filterAndSortProcessors( + [p1, p2], + mockServices.rootConfig({ + data: { + catalog: { + processorOptions: { + processor1: { priority: 'somewhere in the middle, roughly' }, + }, + }, + }, + }), + ), + ).toThrowErrorMatchingInlineSnapshot( + `"Unable to convert config value for key 'catalog.processorOptions.processor1.priority' in 'mock-config' to a number"`, + ); + }); +}); + +describe('filterProviders', () => { + it('should filter providers', () => { + const p1 = { getProviderName: () => 'provider1', connect: jest.fn() }; + const p2 = { getProviderName: () => 'provider2', connect: jest.fn() }; + + expect( + filterProviders([p1, p2], mockServices.rootConfig({ data: {} })), + ).toEqual([p1, p2]); + + expect( + filterProviders( + [p1, p2], + mockServices.rootConfig({ + data: { + catalog: { + providerOptions: { + provider1: { disabled: true }, + }, + }, + }, + }), + ), + ).toEqual([p2]); + + expect( + filterProviders( + [p1, p2], + mockServices.rootConfig({ + data: { + catalog: { + providerOptions: { + provider2: { disabled: true }, + }, + }, + }, + }), + ), + ).toEqual([p1]); + }); + + it('rejects invalid config', () => { + const p1 = { getProviderName: () => 'provider1', connect: jest.fn() }; + const p2 = { getProviderName: () => 'provider2', connect: jest.fn() }; + + expect(() => + filterProviders( + [p1, p2], + mockServices.rootConfig({ + data: { + catalog: { + providerOptions: { + provider1: { disabled: 'i guess so, maybe' }, + }, + }, + }, + }), + ), + ).toThrowErrorMatchingInlineSnapshot( + `"Unable to convert config value for key 'catalog.providerOptions.provider1.disabled' in 'mock-config' to a boolean"`, + ); + }); +}); diff --git a/plugins/catalog-backend/src/service/util.ts b/plugins/catalog-backend/src/service/util.ts index 9847cb7225..2d3d4879cb 100644 --- a/plugins/catalog-backend/src/service/util.ts +++ b/plugins/catalog-backend/src/service/util.ts @@ -24,12 +24,17 @@ import { QueryEntitiesInitialRequest, QueryEntitiesRequest, } from '../catalog/types'; -import { EntityFilter } from '@backstage/plugin-catalog-node'; +import { + CatalogProcessor, + EntityFilter, + EntityProvider, +} from '@backstage/plugin-catalog-node'; import { Entity, parseEntityRef, stringifyEntityRef, } from '@backstage/catalog-model'; +import { Config } from '@backstage/config'; export async function requireRequestBody(req: Request): Promise { const contentType = req.header('content-type'); @@ -166,3 +171,80 @@ export function expandLegacyCompoundRelationsInEntity(entity: Entity): Entity { } return entity; } + +/** + * Given a list of catalog processors, filter out the ones that are disabled + * through the `catalog.processorOptions` config and sort them by priority. + */ +export function filterAndSortProcessors( + processors: CatalogProcessor[], + config: Config, +): CatalogProcessor[] { + function getProcessorOptions( + processor: CatalogProcessor, + ): Config | undefined { + const root = config.getOptionalConfig('catalog.processorOptions'); + try { + return root?.getOptionalConfig(processor.getProcessorName()); + } catch { + // We silence errors specifically here, to cover for cases where the + // processor name contains special characters which makes the config + // reader throw an error. + return undefined; + } + } + + function isProcessorDisabled(processor: CatalogProcessor): boolean { + return ( + getProcessorOptions(processor)?.getOptionalBoolean('disabled') === true + ); + } + + function getProcessorPriority(processor: CatalogProcessor): number { + let priority = + getProcessorOptions(processor)?.getOptionalNumber('priority'); + + if (priority === undefined) { + try { + priority = processor.getPriority?.(); + } catch { + // In case the processor method throws, just return default priority + } + } + + return priority ?? 20; + } + + return processors + .filter(p => !isProcessorDisabled(p)) + .sort((a, b) => getProcessorPriority(a) - getProcessorPriority(b)); +} + +/** + * Given a list of entity providers, filter out the ones that are disabled + * through the `catalog.providerOptions` config. + */ +export function filterProviders( + providers: EntityProvider[], + config: Config, +): EntityProvider[] { + function getProviderOptions(provider: EntityProvider): Config | undefined { + const root = config.getOptionalConfig('catalog.providerOptions'); + try { + return root?.getOptionalConfig(provider.getProviderName()); + } catch { + // We silence errors specifically here, to cover for cases where the + // provider name contains special characters which makes the config + // reader throw an error. + return undefined; + } + } + + function isProviderDisabled(provider: EntityProvider): boolean { + return ( + getProviderOptions(provider)?.getOptionalBoolean('disabled') === true + ); + } + + return providers.filter(p => !isProviderDisabled(p)); +} diff --git a/plugins/catalog-backend/src/tests/integration.test.ts b/plugins/catalog-backend/src/tests/integration.test.ts index 719c5e6ccf..39871583af 100644 --- a/plugins/catalog-backend/src/tests/integration.test.ts +++ b/plugins/catalog-backend/src/tests/integration.test.ts @@ -244,6 +244,7 @@ class TestHarness { const processingDatabase = new DefaultProcessingDatabase({ database: options.db, logger, + events: mockServices.events.mock(), refreshInterval: () => 0.05, }); @@ -273,7 +274,6 @@ class TestHarness { logger, parser: defaultEntityDataParser, policy: EntityPolicies.allOf([]), - legacySingleProcessorValidation: false, }); const stitcher = DefaultStitcher.fromConfig(config, { knex: options.db, @@ -296,12 +296,14 @@ class TestHarness { knex: options.db, orchestrator, stitcher, + scheduler: mockServices.scheduler(), createHash: () => createHash('sha1'), pollingIntervalMs: 50, onProcessingError: event => { proxyProgressTracker.reportError(event.unprocessedEntity, event.errors); }, tracker: proxyProgressTracker, + events: mockServices.events.mock(), }); const refresh = new DefaultRefreshService({ database: catalogDatabase }); diff --git a/plugins/catalog-backend/src/tests/performance/getProcessableEntitiesPerformance.test.ts b/plugins/catalog-backend/src/tests/performance/getProcessableEntitiesPerformance.test.ts index e2ed160b42..a1cc1d3238 100644 --- a/plugins/catalog-backend/src/tests/performance/getProcessableEntitiesPerformance.test.ts +++ b/plugins/catalog-backend/src/tests/performance/getProcessableEntitiesPerformance.test.ts @@ -79,6 +79,7 @@ describePerformanceTest('getProcessableEntities', () => { const sut = new DefaultProcessingDatabase({ database: knex, logger: mockServices.logger.mock(), + events: mockServices.events.mock(), refreshInterval: () => 0, }); diff --git a/plugins/catalog-common/CHANGELOG.md b/plugins/catalog-common/CHANGELOG.md index a0cdac70e8..a536481835 100644 --- a/plugins/catalog-common/CHANGELOG.md +++ b/plugins/catalog-common/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-catalog-common +## 1.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-search-common@1.2.20-next.0 + ## 1.1.5 ### Patch Changes diff --git a/plugins/catalog-common/package.json b/plugins/catalog-common/package.json index 7ce4e23d55..9ee7c8d416 100644 --- a/plugins/catalog-common/package.json +++ b/plugins/catalog-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-common", - "version": "1.1.5", + "version": "1.1.6-next.0", "description": "Common functionalities for the catalog plugin", "backstage": { "role": "common-library", diff --git a/plugins/catalog-graph/CHANGELOG.md b/plugins/catalog-graph/CHANGELOG.md index 8d19916dab..b492c41a37 100644 --- a/plugins/catalog-graph/CHANGELOG.md +++ b/plugins/catalog-graph/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-catalog-graph +## 0.5.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/core-compat-api@0.5.3-next.1 + - @backstage/plugin-catalog-react@1.21.2-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/catalog-client@1.12.0 + +## 0.5.2-next.0 + +### Patch Changes + +- 87b5e6e: Add missing API implementation for catalog graph plugin in NFS apps. +- 6981ae6: Fixed DependencyGraph `svg` size not adapting to the container size +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/core-components@0.18.2-next.0 + - @backstage/core-compat-api@0.5.3-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/catalog-client@1.12.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.11.0 + - @backstage/types@1.2.2 + ## 0.5.0 ### Minor Changes diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index 86bd930095..f455dbce31 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.0", + "version": "0.5.2-next.1", "backstage": { "role": "frontend-plugin", "pluginId": "catalog-graph", diff --git a/plugins/catalog-graph/report-alpha.api.md b/plugins/catalog-graph/report-alpha.api.md index fe852d8cf6..edf84e936a 100644 --- a/plugins/catalog-graph/report-alpha.api.md +++ b/plugins/catalog-graph/report-alpha.api.md @@ -3,11 +3,14 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; import { Direction } from '@backstage/plugin-catalog-graph'; import { Entity } from '@backstage/catalog-model'; 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 { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; @@ -58,6 +61,21 @@ const _default: OverridableFrontendPlugin< }>; }, { + 'api:catalog-graph': ExtensionDefinition<{ + kind: 'api'; + name: undefined; + config: {}; + configInput: {}; + output: ExtensionDataRef; + inputs: {}; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; + }>; 'entity-card:catalog-graph/relations': ExtensionDefinition<{ config: { kinds: string[] | undefined; diff --git a/plugins/catalog-graph/src/alpha.tsx b/plugins/catalog-graph/src/alpha.tsx index 5fc43f1b4a..9a7f15e553 100644 --- a/plugins/catalog-graph/src/alpha.tsx +++ b/plugins/catalog-graph/src/alpha.tsx @@ -15,6 +15,7 @@ */ import { + ApiBlueprint, createFrontendPlugin, PageBlueprint, } from '@backstage/frontend-plugin-api'; @@ -24,7 +25,11 @@ import { } from '@backstage/core-compat-api'; import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha'; import { catalogGraphRouteRef, catalogEntityRouteRef } from './routes'; -import { Direction } from '@backstage/plugin-catalog-graph'; +import { + catalogGraphApiRef, + DefaultCatalogGraphApi, + Direction, +} from '@backstage/plugin-catalog-graph'; const CatalogGraphEntityCard = EntityCardBlueprint.makeWithOverrides({ name: 'relations', @@ -85,6 +90,15 @@ const CatalogGraphPage = PageBlueprint.makeWithOverrides({ }, }); +const CatalogGraphApi = ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: catalogGraphApiRef, + deps: {}, + factory: () => new DefaultCatalogGraphApi(), + }), +}); + export default createFrontendPlugin({ pluginId: 'catalog-graph', info: { packageJson: () => import('../package.json') }, @@ -94,7 +108,7 @@ export default createFrontendPlugin({ externalRoutes: { catalogEntity: convertLegacyRouteRef(catalogEntityRouteRef), }, - extensions: [CatalogGraphPage, CatalogGraphEntityCard], + extensions: [CatalogGraphPage, CatalogGraphEntityCard, CatalogGraphApi], }); export { catalogGraphTranslationRef } from './translation'; diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.tsx b/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.tsx index f77ca4b37d..17b5861173 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.tsx +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.tsx @@ -158,6 +158,7 @@ export const EntityRelationsGraph = (props: EntityRelationsGraphProps) => { renderLabel={renderLabel || DefaultRenderLabel} direction={direction} className={classes.graph} + fit="contain" paddingX={theme.spacing(4)} paddingY={theme.spacing(4)} labelPosition={DependencyGraphTypes.LabelPosition.RIGHT} diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index 9c8778e168..7cd77df78b 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,39 @@ # @backstage/plugin-catalog-import +## 0.13.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/integration-react@1.2.11-next.1 + - @backstage/core-compat-api@0.5.3-next.1 + - @backstage/plugin-catalog-react@1.21.2-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/plugin-catalog-common@1.1.6-next.0 + - @backstage/catalog-client@1.12.0 + +## 0.13.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/core-components@0.18.2-next.0 + - @backstage/integration@1.18.1-next.0 + - @backstage/core-compat-api@0.5.3-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/integration-react@1.2.11-next.0 + - @backstage/catalog-client@1.12.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-plugin-api@1.11.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.5 + ## 0.13.5 ### Patch Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 97b7283ab9..80524e0fde 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.5", + "version": "0.13.6-next.1", "description": "A Backstage plugin the helps you import entities into your catalog", "backstage": { "role": "frontend-plugin", diff --git a/plugins/catalog-node/CHANGELOG.md b/plugins/catalog-node/CHANGELOG.md index ba27f2a2e2..c61acc0a30 100644 --- a/plugins/catalog-node/CHANGELOG.md +++ b/plugins/catalog-node/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-node +## 1.19.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-permission-node@0.10.5-next.0 + - @backstage/plugin-catalog-common@1.1.6-next.0 + - @backstage/catalog-client@1.12.0 + ## 1.19.0 ### Minor Changes diff --git a/plugins/catalog-node/package.json b/plugins/catalog-node/package.json index 545656ae80..378068b773 100644 --- a/plugins/catalog-node/package.json +++ b/plugins/catalog-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-node", - "version": "1.19.0", + "version": "1.19.1-next.0", "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 f5e4d26638..b4088aaaf7 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,42 @@ # @backstage/plugin-catalog-react +## 1.21.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/frontend-test-utils@0.3.7-next.1 + - @backstage/integration-react@1.2.11-next.1 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-permission-react@0.4.37-next.0 + - @backstage/core-compat-api@0.5.3-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/plugin-catalog-common@1.1.6-next.0 + - @backstage/catalog-client@1.12.0 + +## 1.21.2-next.0 + +### Patch Changes + +- 2a3704d: Correct translation key from "type" to "owner" for owner column in entity table to ensure the right translation is loaded. +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/core-compat-api@0.5.3-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/integration-react@1.2.11-next.0 + - @backstage/catalog-client@1.12.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.11.0 + - @backstage/errors@1.2.7 + - @backstage/frontend-test-utils@0.3.7-next.0 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-common@0.9.1 + - @backstage/plugin-permission-react@0.4.36 + ## 1.21.0 ### Minor Changes diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index dabfb55d11..cb55343b0a 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.0", + "version": "1.21.2-next.1", "description": "A frontend library that helps other Backstage plugins interact with the catalog", "backstage": { "role": "web-library", diff --git a/plugins/catalog-react/src/components/EntityTable/columns.test.tsx b/plugins/catalog-react/src/components/EntityTable/columns.test.tsx new file mode 100644 index 0000000000..05c9c61c26 --- /dev/null +++ b/plugins/catalog-react/src/components/EntityTable/columns.test.tsx @@ -0,0 +1,39 @@ +/* + * 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 { render, screen } from '@testing-library/react'; +import { columnFactories } from './columns'; +import { mockApis, MockErrorApi, TestApiProvider } from '@backstage/test-utils'; +import { errorApiRef } from '@backstage/core-plugin-api'; +import { translationApiRef } from '@backstage/core-plugin-api/alpha'; + +describe('columns', () => { + it('should render owner title', async () => { + const { title } = columnFactories.createOwnerColumn(); + + render( + + {title} + , + ); + + expect(await screen.findByText('Owner')).toBeInTheDocument(); + }); +}); diff --git a/plugins/catalog-react/src/components/EntityTable/columns.tsx b/plugins/catalog-react/src/components/EntityTable/columns.tsx index 94b1141057..1bea56dc27 100644 --- a/plugins/catalog-react/src/components/EntityTable/columns.tsx +++ b/plugins/catalog-react/src/components/EntityTable/columns.tsx @@ -108,7 +108,7 @@ export const columnFactories = Object.freeze({ }, createOwnerColumn(): TableColumn { return this.createEntityRelationColumn({ - title: , + title: , relation: RELATION_OWNED_BY, defaultKind: 'group', }); diff --git a/plugins/catalog-unprocessed-entities-common/CHANGELOG.md b/plugins/catalog-unprocessed-entities-common/CHANGELOG.md index d72fea4dd6..9e256e7b77 100644 --- a/plugins/catalog-unprocessed-entities-common/CHANGELOG.md +++ b/plugins/catalog-unprocessed-entities-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-catalog-unprocessed-entities-common +## 0.0.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.2-next.0 + ## 0.0.9 ### Patch Changes diff --git a/plugins/catalog-unprocessed-entities-common/package.json b/plugins/catalog-unprocessed-entities-common/package.json index 6663c2535d..cd8aa8e4f0 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.9", + "version": "0.0.10-next.0", "description": "Common functionalities for the catalog-unprocessed-entities plugin", "backstage": { "role": "common-library", diff --git a/plugins/catalog-unprocessed-entities/CHANGELOG.md b/plugins/catalog-unprocessed-entities/CHANGELOG.md index 31667429ca..6a6d24b25c 100644 --- a/plugins/catalog-unprocessed-entities/CHANGELOG.md +++ b/plugins/catalog-unprocessed-entities/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-catalog-unprocessed-entities +## 0.2.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/core-compat-api@0.5.3-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + +## 0.2.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/core-compat-api@0.5.3-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.11.0 + - @backstage/errors@1.2.7 + ## 0.2.21 ### Patch Changes diff --git a/plugins/catalog-unprocessed-entities/package.json b/plugins/catalog-unprocessed-entities/package.json index a2c1d617e5..e3ecde30c4 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.21", + "version": "0.2.22-next.1", "backstage": { "role": "frontend-plugin", "pluginId": "catalog-unprocessed-entities", diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 8ae3b4c8da..e260fb05b7 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,48 @@ # @backstage/plugin-catalog +## 1.31.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/integration-react@1.2.11-next.1 + - @backstage/plugin-permission-react@0.4.37-next.0 + - @backstage/plugin-techdocs-react@1.3.4-next.1 + - @backstage/core-compat-api@0.5.3-next.1 + - @backstage/plugin-catalog-react@1.21.2-next.1 + - @backstage/plugin-search-react@1.9.5-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/plugin-scaffolder-common@1.7.2-next.1 + - @backstage/plugin-catalog-common@1.1.6-next.0 + - @backstage/plugin-search-common@1.2.20-next.0 + - @backstage/catalog-client@1.12.0 + +## 1.31.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/core-components@0.18.2-next.0 + - @backstage/core-compat-api@0.5.3-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/integration-react@1.2.11-next.0 + - @backstage/plugin-search-react@1.9.5-next.0 + - @backstage/plugin-techdocs-react@1.3.4-next.0 + - @backstage/plugin-scaffolder-common@1.7.2-next.0 + - @backstage/catalog-client@1.12.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.11.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.5 + - @backstage/plugin-permission-react@0.4.36 + - @backstage/plugin-search-common@1.2.19 + - @backstage/plugin-techdocs-common@0.1.1 + ## 1.31.3 ### Patch Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 2a7a956c37..95c843e504 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "1.31.3", + "version": "1.31.4-next.1", "description": "The Backstage plugin for browsing the Backstage catalog", "backstage": { "role": "frontend-plugin", diff --git a/plugins/config-schema/CHANGELOG.md b/plugins/config-schema/CHANGELOG.md index 74c7a68e58..15b30e6845 100644 --- a/plugins/config-schema/CHANGELOG.md +++ b/plugins/config-schema/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-config-schema +## 0.1.73-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + +## 0.1.73-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/core-plugin-api@1.11.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + ## 0.1.72 ### Patch Changes diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index 78addfd9c2..c87141b1f3 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.72", + "version": "0.1.73-next.1", "description": "A Backstage plugin that lets you browse the configuration schema of your app", "backstage": { "role": "frontend-plugin", diff --git a/plugins/devtools-backend/CHANGELOG.md b/plugins/devtools-backend/CHANGELOG.md index eeaeda6605..e48d8fe67c 100644 --- a/plugins/devtools-backend/CHANGELOG.md +++ b/plugins/devtools-backend/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/plugin-devtools-backend +## 0.5.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.10.4-next.0 + - @backstage/config@1.3.4-next.0 + - @backstage/backend-defaults@0.13.0-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-permission-node@0.10.5-next.0 + - @backstage/plugin-devtools-common@0.1.18-next.0 + +## 0.5.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.0-next.0 + - @backstage/plugin-permission-node@0.10.4 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.3 + - @backstage/config-loader@1.10.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-devtools-common@0.1.17 + - @backstage/plugin-permission-common@0.9.1 + ## 0.5.9 ### Patch Changes diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json index 333a89d158..fc3c60e5d9 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.9", + "version": "0.5.10-next.1", "backstage": { "role": "backend-plugin", "pluginId": "devtools", diff --git a/plugins/devtools-common/CHANGELOG.md b/plugins/devtools-common/CHANGELOG.md index 55ed84de0f..b4d0c31621 100644 --- a/plugins/devtools-common/CHANGELOG.md +++ b/plugins/devtools-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-devtools-common +## 0.1.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.2-next.0 + ## 0.1.17 ### Patch Changes diff --git a/plugins/devtools-common/package.json b/plugins/devtools-common/package.json index ec39a0809d..fe8bfe931d 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.17", + "version": "0.1.18-next.0", "description": "Common functionalities for the devtools plugin", "backstage": { "role": "common-library", diff --git a/plugins/devtools/CHANGELOG.md b/plugins/devtools/CHANGELOG.md index de537019e3..99e894a877 100644 --- a/plugins/devtools/CHANGELOG.md +++ b/plugins/devtools/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-devtools +## 0.1.32-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/plugin-permission-react@0.4.37-next.0 + - @backstage/core-compat-api@0.5.3-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/plugin-devtools-common@0.1.18-next.0 + +## 0.1.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/core-compat-api@0.5.3-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/core-plugin-api@1.11.0 + - @backstage/errors@1.2.7 + - @backstage/plugin-devtools-common@0.1.17 + - @backstage/plugin-permission-react@0.4.36 + ## 0.1.31 ### Patch Changes diff --git a/plugins/devtools/package.json b/plugins/devtools/package.json index 3da6e8e02c..8d6400b443 100644 --- a/plugins/devtools/package.json +++ b/plugins/devtools/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools", - "version": "0.1.31", + "version": "0.1.32-next.1", "backstage": { "role": "frontend-plugin", "pluginId": "devtools", diff --git a/plugins/events-backend-module-aws-sqs/CHANGELOG.md b/plugins/events-backend-module-aws-sqs/CHANGELOG.md index baca97880c..58cca92f47 100644 --- a/plugins/events-backend-module-aws-sqs/CHANGELOG.md +++ b/plugins/events-backend-module-aws-sqs/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-events-backend-module-aws-sqs +## 0.4.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + ## 0.4.15 ### Patch Changes diff --git a/plugins/events-backend-module-aws-sqs/package.json b/plugins/events-backend-module-aws-sqs/package.json index 359cd768c8..36ea2e4cad 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.15", + "version": "0.4.16-next.0", "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 91791d0966..99e91bb475 100644 --- a/plugins/events-backend-module-azure/CHANGELOG.md +++ b/plugins/events-backend-module-azure/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-events-backend-module-azure +## 0.2.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + ## 0.2.24 ### Patch Changes diff --git a/plugins/events-backend-module-azure/package.json b/plugins/events-backend-module-azure/package.json index 3747c2b097..a6b93f76c3 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.24", + "version": "0.2.25-next.0", "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 5014807859..25ba6c3ed3 100644 --- a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-events-backend-module-bitbucket-cloud +## 0.2.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + ## 0.2.24 ### Patch Changes diff --git a/plugins/events-backend-module-bitbucket-cloud/package.json b/plugins/events-backend-module-bitbucket-cloud/package.json index e523b668ca..b653428ae7 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.24", + "version": "0.2.25-next.0", "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 8c54c534b3..88a40b4114 100644 --- a/plugins/events-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/events-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-events-backend-module-bitbucket-server +## 0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + ## 0.1.5 ### Patch Changes diff --git a/plugins/events-backend-module-bitbucket-server/package.json b/plugins/events-backend-module-bitbucket-server/package.json index dd4429548c..b7365f0d3d 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.5", + "version": "0.1.6-next.0", "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 511a31488d..8938f8ff6d 100644 --- a/plugins/events-backend-module-gerrit/CHANGELOG.md +++ b/plugins/events-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-events-backend-module-gerrit +## 0.2.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + ## 0.2.24 ### Patch Changes diff --git a/plugins/events-backend-module-gerrit/package.json b/plugins/events-backend-module-gerrit/package.json index 81978168a7..dfd74ab6bd 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.24", + "version": "0.2.25-next.0", "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 28495671fb..0eef3b4c4c 100644 --- a/plugins/events-backend-module-github/CHANGELOG.md +++ b/plugins/events-backend-module-github/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-events-backend-module-github +## 0.4.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration@1.18.1-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + +## 0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.18.1-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/config@1.3.3 + - @backstage/types@1.2.2 + - @backstage/plugin-events-node@0.4.15 + ## 0.4.4 ### Patch Changes diff --git a/plugins/events-backend-module-github/package.json b/plugins/events-backend-module-github/package.json index aebbc6eddf..be65aa0744 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.4", + "version": "0.4.5-next.1", "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 0df6ece6ab..68f72cdfd1 100644 --- a/plugins/events-backend-module-gitlab/CHANGELOG.md +++ b/plugins/events-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-events-backend-module-gitlab +## 0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + ## 0.3.5 ### Patch Changes diff --git a/plugins/events-backend-module-gitlab/package.json b/plugins/events-backend-module-gitlab/package.json index 560f4fae76..63a15f990d 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.5", + "version": "0.3.6-next.0", "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 6002004dc4..4d296461b0 100644 --- a/plugins/events-backend-module-google-pubsub/CHANGELOG.md +++ b/plugins/events-backend-module-google-pubsub/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-events-backend-module-google-pubsub +## 0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + ## 0.1.4 ### Patch Changes diff --git a/plugins/events-backend-module-google-pubsub/package.json b/plugins/events-backend-module-google-pubsub/package.json index 88f6fe714e..4b52f60661 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.4", + "version": "0.1.5-next.0", "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 f12109da75..b3b94438cd 100644 --- a/plugins/events-backend-module-kafka/CHANGELOG.md +++ b/plugins/events-backend-module-kafka/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-events-backend-module-kafka +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + ## 0.1.3 ### Patch Changes diff --git a/plugins/events-backend-module-kafka/package.json b/plugins/events-backend-module-kafka/package.json index 03a1c6c1dc..d298375b55 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.3", + "version": "0.1.4-next.0", "description": "The kafka backend module for the events plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/events-backend-test-utils/CHANGELOG.md b/plugins/events-backend-test-utils/CHANGELOG.md index 5a02e4ed51..bb2cced423 100644 --- a/plugins/events-backend-test-utils/CHANGELOG.md +++ b/plugins/events-backend-test-utils/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-events-backend-test-utils +## 0.1.49-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.16-next.0 + ## 0.1.48 ### Patch Changes diff --git a/plugins/events-backend-test-utils/package.json b/plugins/events-backend-test-utils/package.json index a27e4efbe6..9ef009f4cf 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.48", + "version": "0.1.49-next.0", "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 15b1b91730..825199efef 100644 --- a/plugins/events-backend/CHANGELOG.md +++ b/plugins/events-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-events-backend +## 0.5.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/backend-openapi-utils@0.6.2-next.0 + - @backstage/plugin-events-node@0.4.16-next.0 + ## 0.5.6 ### Patch Changes diff --git a/plugins/events-backend/package.json b/plugins/events-backend/package.json index a5f530f33c..1094448ac5 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.6", + "version": "0.5.7-next.0", "backstage": { "role": "backend-plugin", "pluginId": "events", diff --git a/plugins/events-node/CHANGELOG.md b/plugins/events-node/CHANGELOG.md index 1b706fdf22..152b45c4f0 100644 --- a/plugins/events-node/CHANGELOG.md +++ b/plugins/events-node/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-events-node +## 0.4.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + ## 0.4.15 ### Patch Changes diff --git a/plugins/events-node/package.json b/plugins/events-node/package.json index efd879a47a..3d1cbb949c 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.15", + "version": "0.4.16-next.0", "description": "The plugin-events-node module for @backstage/plugin-events-backend", "backstage": { "role": "node-library", diff --git a/plugins/example-todo-list-backend/CHANGELOG.md b/plugins/example-todo-list-backend/CHANGELOG.md index bd1db36bb8..da8317ef8f 100644 --- a/plugins/example-todo-list-backend/CHANGELOG.md +++ b/plugins/example-todo-list-backend/CHANGELOG.md @@ -1,5 +1,12 @@ # @internal/plugin-todo-list-backend +## 1.0.44-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + ## 1.0.43 ### Patch Changes diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json index 6775feb352..eaaa88f919 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.43", + "version": "1.0.44-next.0", "backstage": { "role": "backend-plugin", "pluginId": "todo-list", diff --git a/plugins/example-todo-list-common/CHANGELOG.md b/plugins/example-todo-list-common/CHANGELOG.md index 72a97d5d14..8f7d8cde0f 100644 --- a/plugins/example-todo-list-common/CHANGELOG.md +++ b/plugins/example-todo-list-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @internal/plugin-todo-list-common +## 1.0.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.2-next.0 + ## 1.0.26 ### Patch Changes diff --git a/plugins/example-todo-list-common/package.json b/plugins/example-todo-list-common/package.json index 3858b463dc..ea144ba06d 100644 --- a/plugins/example-todo-list-common/package.json +++ b/plugins/example-todo-list-common/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list-common", - "version": "1.0.26", + "version": "1.0.27-next.0", "backstage": { "role": "common-library", "pluginId": "todo-list", diff --git a/plugins/example-todo-list/CHANGELOG.md b/plugins/example-todo-list/CHANGELOG.md index 621859a9ab..67d827c298 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.44-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + +## 1.0.44-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/core-plugin-api@1.11.0 + ## 1.0.43 ### Patch Changes diff --git a/plugins/example-todo-list/package.json b/plugins/example-todo-list/package.json index f0273e482b..723cab4ed1 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.43", + "version": "1.0.44-next.1", "backstage": { "role": "frontend-plugin", "pluginId": "todo-list", diff --git a/plugins/gateway-backend/CHANGELOG.md b/plugins/gateway-backend/CHANGELOG.md index eabcf7fe66..933a55625c 100644 --- a/plugins/gateway-backend/CHANGELOG.md +++ b/plugins/gateway-backend/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-gateway-backend +## 1.0.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + ## 1.0.5 ### Patch Changes diff --git a/plugins/gateway-backend/package.json b/plugins/gateway-backend/package.json index 985f5f9380..1503fd8668 100644 --- a/plugins/gateway-backend/package.json +++ b/plugins/gateway-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-gateway-backend", - "version": "1.0.5", + "version": "1.0.6-next.0", "backstage": { "role": "backend-plugin", "pluginId": "gateway", diff --git a/plugins/home-react/CHANGELOG.md b/plugins/home-react/CHANGELOG.md index b4fff66b63..c3f58c0cb3 100644 --- a/plugins/home-react/CHANGELOG.md +++ b/plugins/home-react/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-home-react +## 0.1.31-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.1 + +## 0.1.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/core-plugin-api@1.11.0 + ## 0.1.30 ### Patch Changes diff --git a/plugins/home-react/package.json b/plugins/home-react/package.json index bce8419b08..3d2b97c535 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.30", + "version": "0.1.31-next.1", "description": "A Backstage plugin that contains react components helps you build a home page", "backstage": { "role": "web-library", diff --git a/plugins/home/CHANGELOG.md b/plugins/home/CHANGELOG.md index 5731706ece..68ef6448a5 100644 --- a/plugins/home/CHANGELOG.md +++ b/plugins/home/CHANGELOG.md @@ -1,5 +1,45 @@ # @backstage/plugin-home +## 0.8.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.2 + - @backstage/theme@0.6.9-next.0 + +## 0.8.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/core-app-api@1.19.1-next.0 + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/core-compat-api@0.5.3-next.1 + - @backstage/plugin-catalog-react@1.21.2-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/plugin-home-react@0.1.31-next.1 + - @backstage/catalog-client@1.12.0 + +## 0.8.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/core-components@0.18.2-next.0 + - @backstage/core-compat-api@0.5.3-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/plugin-home-react@0.1.31-next.0 + - @backstage/catalog-client@1.12.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/core-app-api@1.19.0 + - @backstage/core-plugin-api@1.11.0 + - @backstage/theme@0.6.8 + ## 0.8.12 ### Patch Changes diff --git a/plugins/home/package.json b/plugins/home/package.json index a348b8eff9..910d6c036f 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home", - "version": "0.8.12", + "version": "0.8.13-next.2", "description": "A Backstage plugin that helps you build a home page", "backstage": { "role": "frontend-plugin", diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index 1ede69fa30..5221cbf95f 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,40 @@ # @backstage/plugin-kubernetes-backend +## 0.20.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.4-next.0 + - @backstage/integration-aws-node@0.1.18-next.0 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-auth-node@0.6.8-next.0 + - @backstage/plugin-permission-common@0.9.2-next.0 + - @backstage/plugin-permission-node@0.10.5-next.0 + - @backstage/plugin-kubernetes-node@0.3.5-next.1 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/plugin-kubernetes-common@0.9.7-next.1 + - @backstage/catalog-client@1.12.0 + +## 0.20.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.9.7-next.0 + - @backstage/plugin-auth-node@0.6.7 + - @backstage/plugin-kubernetes-node@0.3.5-next.0 + - @backstage/plugin-permission-node@0.10.4 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-client@1.12.0 + - @backstage/catalog-model@1.7.5 + - @backstage/config@1.3.3 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.17 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.19.0 + - @backstage/plugin-permission-common@0.9.1 + ## 0.20.2 ### Patch Changes diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index 6d029eae95..f90c2ff4a9 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.2", + "version": "0.20.3-next.1", "description": "A Backstage backend plugin that integrates towards Kubernetes", "backstage": { "role": "backend-plugin", diff --git a/plugins/kubernetes-cluster/CHANGELOG.md b/plugins/kubernetes-cluster/CHANGELOG.md index 7f7cf88809..527c13c88d 100644 --- a/plugins/kubernetes-cluster/CHANGELOG.md +++ b/plugins/kubernetes-cluster/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-kubernetes-cluster +## 0.0.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/plugin-permission-react@0.4.37-next.0 + - @backstage/plugin-catalog-react@1.21.2-next.1 + - @backstage/plugin-kubernetes-react@0.5.12-next.1 + - @backstage/plugin-kubernetes-common@0.9.7-next.1 + +## 0.0.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/core-components@0.18.2-next.0 + - @backstage/plugin-kubernetes-common@0.9.7-next.0 + - @backstage/plugin-kubernetes-react@0.5.12-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.11.0 + - @backstage/plugin-permission-react@0.4.36 + ## 0.0.29 ### Patch Changes diff --git a/plugins/kubernetes-cluster/package.json b/plugins/kubernetes-cluster/package.json index e789c3edaf..3e0544ced9 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.29", + "version": "0.0.30-next.1", "description": "A Backstage plugin that shows details of Kubernetes clusters", "backstage": { "role": "frontend-plugin", diff --git a/plugins/kubernetes-common/CHANGELOG.md b/plugins/kubernetes-common/CHANGELOG.md index 64ea6455e4..3c9078d364 100644 --- a/plugins/kubernetes-common/CHANGELOG.md +++ b/plugins/kubernetes-common/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-kubernetes-common +## 0.9.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.9.2-next.0 + +## 0.9.7-next.0 + +### Patch Changes + +- bdd7f95: Make SERVICEACCOUNT_CA_PATH public so it can be imported by external modules. +- Updated dependencies + - @backstage/catalog-model@1.7.5 + - @backstage/types@1.2.2 + - @backstage/plugin-permission-common@0.9.1 + ## 0.9.6 ### Patch Changes diff --git a/plugins/kubernetes-common/package.json b/plugins/kubernetes-common/package.json index cde58de050..18dc397817 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.6", + "version": "0.9.7-next.1", "description": "Common functionalities for kubernetes, to be shared between kubernetes and kubernetes-backend plugin", "backstage": { "role": "common-library", diff --git a/plugins/kubernetes-node/CHANGELOG.md b/plugins/kubernetes-node/CHANGELOG.md index d7366fa90c..e6d163150c 100644 --- a/plugins/kubernetes-node/CHANGELOG.md +++ b/plugins/kubernetes-node/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-kubernetes-node +## 0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-kubernetes-common@0.9.7-next.1 + +## 0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.9.7-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-model@1.7.5 + - @backstage/types@1.2.2 + ## 0.3.4 ### Patch Changes diff --git a/plugins/kubernetes-node/package.json b/plugins/kubernetes-node/package.json index 2c4c043ec5..9b0297a9c2 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.4", + "version": "0.3.5-next.1", "description": "Node.js library for the kubernetes plugin", "backstage": { "role": "node-library", diff --git a/plugins/kubernetes-react/CHANGELOG.md b/plugins/kubernetes-react/CHANGELOG.md index c542907fe6..7704c954b2 100644 --- a/plugins/kubernetes-react/CHANGELOG.md +++ b/plugins/kubernetes-react/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-kubernetes-react +## 0.5.12-next.2 + +### Patch Changes + +- ac405f2: The configmaps added to be rendered +- f7a4144: Fixes calculation of CPU utilization in the PodTable +- Updated dependencies + - @backstage/core-components@0.18.2-next.2 + +## 0.5.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/plugin-kubernetes-common@0.9.7-next.1 + +## 0.5.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.0 + - @backstage/plugin-kubernetes-common@0.9.7-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.11.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + ## 0.5.11 ### Patch Changes diff --git a/plugins/kubernetes-react/package.json b/plugins/kubernetes-react/package.json index 82e3d59d8b..76d1a08cdf 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.11", + "version": "0.5.12-next.2", "description": "Web library for the kubernetes-react plugin", "backstage": { "role": "web-library", diff --git a/plugins/kubernetes-react/src/__fixtures__/1-configmaps.json b/plugins/kubernetes-react/src/__fixtures__/1-configmaps.json new file mode 100644 index 0000000000..ab0518e87d --- /dev/null +++ b/plugins/kubernetes-react/src/__fixtures__/1-configmaps.json @@ -0,0 +1,26 @@ +{ + "configMaps": [ + { + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "app-config", + "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.host": "localhost", + "database.port": "5432", + "app.name": "dice-roller", + "app.version": "1.0.0" + } + } + ] +} diff --git a/plugins/kubernetes-react/src/__fixtures__/2-configmaps.json b/plugins/kubernetes-react/src/__fixtures__/2-configmaps.json new file mode 100644 index 0000000000..2c0c7f8121 --- /dev/null +++ b/plugins/kubernetes-react/src/__fixtures__/2-configmaps.json @@ -0,0 +1,47 @@ +{ + "configMaps": [ + { + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "app-config", + "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.host": "localhost", + "database.port": "5432", + "app.name": "dice-roller", + "app.version": "1.0.0" + } + }, + { + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "redis-config", + "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.conf": "# Redis configuration\nmaxmemory 256mb\nmaxmemory-policy allkeys-lru", + "redis.host": "redis-service", + "redis.port": "6379" + } + } + ] +} diff --git a/plugins/kubernetes-react/src/components/Cluster/Cluster.tsx b/plugins/kubernetes-react/src/components/Cluster/Cluster.tsx index 642ed642a4..4e8c4a1947 100644 --- a/plugins/kubernetes-react/src/components/Cluster/Cluster.tsx +++ b/plugins/kubernetes-react/src/components/Cluster/Cluster.tsx @@ -30,6 +30,7 @@ import { DeploymentsAccordions } from '../DeploymentsAccordions'; import { StatefulSetsAccordions } from '../StatefulSetsAccordions'; import { IngressesAccordions } from '../IngressesAccordions'; import { ServicesAccordions } from '../ServicesAccordions'; +import { ConfigmapsAccordions } from '../ConfigmapsAccordions'; import { CronJobsAccordions } from '../CronJobsAccordions'; import { CustomResources } from '../CustomResources'; import { DaemonSetsAccordions } from '../DaemonSetsAccordions'; @@ -170,6 +171,11 @@ export const Cluster = ({ clusterObjects, podsWithErrors }: ClusterProps) => { ) : undefined} + {groupedResponses.configMaps.length > 0 ? ( + + + + ) : undefined} {groupedResponses.cronJobs.length > 0 ? ( diff --git a/plugins/kubernetes-react/src/components/ConfigmapsAccordions/ConfigmapsAccordions.test.tsx b/plugins/kubernetes-react/src/components/ConfigmapsAccordions/ConfigmapsAccordions.test.tsx new file mode 100644 index 0000000000..d819a01cd9 --- /dev/null +++ b/plugins/kubernetes-react/src/components/ConfigmapsAccordions/ConfigmapsAccordions.test.tsx @@ -0,0 +1,54 @@ +/* + * 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 { ConfigmapsAccordions } from './ConfigmapsAccordions'; +import * as oneConfigmapsFixture from '../../__fixtures__/1-configmaps.json'; +import * as twoConfigmapsFixture from '../../__fixtures__/2-configmaps.json'; +import { renderInTestApp } from '@backstage/test-utils'; +import { kubernetesProviders } from '../../hooks/test-utils'; + +describe('ConfigmapsAccordions', () => { + it('should render 1 configmap', async () => { + const wrapper = kubernetesProviders( + oneConfigmapsFixture, + new Set(), + ); + + await renderInTestApp(wrapper()); + + expect(screen.getByText('app-config')).toBeInTheDocument(); + expect(screen.getByText('ConfigMap')).toBeInTheDocument(); + expect(screen.getByText('namespace: default')).toBeInTheDocument(); + expect(screen.getByText('Data Count: 4')).toBeInTheDocument(); + }); + + it('should render 2 configmaps', async () => { + const wrapper = kubernetesProviders( + twoConfigmapsFixture, + new Set(), + ); + + await renderInTestApp(wrapper()); + + expect(screen.getByText('app-config')).toBeInTheDocument(); + expect(screen.getByText('redis-config')).toBeInTheDocument(); + expect(screen.getAllByText('ConfigMap')).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/ConfigmapsAccordions/ConfigmapsAccordions.tsx b/plugins/kubernetes-react/src/components/ConfigmapsAccordions/ConfigmapsAccordions.tsx new file mode 100644 index 0000000000..bdb5b10d35 --- /dev/null +++ b/plugins/kubernetes-react/src/components/ConfigmapsAccordions/ConfigmapsAccordions.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 { V1ConfigMap } from '@kubernetes/client-node'; +import { ConfigmapsDrawer } from './ConfigmapsDrawer.tsx'; +import { GroupedResponsesContext } from '../../hooks'; +import { StructuredMetadataTable } from '@backstage/core-components'; + +type ConfigmapSummaryProps = { + configmap: V1ConfigMap; +}; + +const ConfigmapSummary = ({ configmap }: ConfigmapSummaryProps) => { + return ( + + + + + + + + Data Count: {configmap.data ? Object.keys(configmap.data).length : 0} + + + + ); +}; + +type ConfigmapsCardProps = { + configmap: V1ConfigMap; +}; + +const ConfigmapCard = ({ configmap }: ConfigmapsCardProps) => { + const metadata: any = {}; + + metadata.data = configmap.data; + + return ( + + ); +}; + +export type ConfigmapsAccordionsProps = {}; + +type ConfigmapsAccordionProps = { + configmap: V1ConfigMap; +}; + +const ConfigmapsAccordion = ({ configmap }: ConfigmapsAccordionProps) => { + return ( + + }> + + + + + + + ); +}; + +export const ConfigmapsAccordions = ({}: ConfigmapsAccordionsProps) => { + const groupedResponses = useContext(GroupedResponsesContext); + return ( + + {groupedResponses.configMaps.map((configmap, i) => ( + + + + ))} + + ); +}; diff --git a/plugins/kubernetes-react/src/components/ConfigmapsAccordions/ConfigmapsDrawer.test.tsx b/plugins/kubernetes-react/src/components/ConfigmapsAccordions/ConfigmapsDrawer.test.tsx new file mode 100644 index 0000000000..cde7265cab --- /dev/null +++ b/plugins/kubernetes-react/src/components/ConfigmapsAccordions/ConfigmapsDrawer.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 oneConfigmapsFixture from '../../__fixtures__/1-configmaps.json'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { ConfigmapsDrawer } from './ConfigmapsDrawer'; +import { kubernetesClusterLinkFormatterApiRef } from '../../api'; + +describe('ConfigmapsDrawer', () => { + it('should render configmap drawer', async () => { + const { getByText, getAllByText } = await renderInTestApp( + + + , + ); + + expect(getAllByText('app-config')).toHaveLength(3); + expect(getAllByText('ConfigMap')).toHaveLength(3); + expect(getByText('YAML')).toBeInTheDocument(); + expect(getByText('namespace: default')).toBeInTheDocument(); + }); +}); diff --git a/plugins/kubernetes-react/src/components/ConfigmapsAccordions/ConfigmapsDrawer.tsx b/plugins/kubernetes-react/src/components/ConfigmapsAccordions/ConfigmapsDrawer.tsx new file mode 100644 index 0000000000..97e8aab723 --- /dev/null +++ b/plugins/kubernetes-react/src/components/ConfigmapsAccordions/ConfigmapsDrawer.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 { V1ConfigMap } from '@kubernetes/client-node'; + +export const ConfigmapsDrawer = ({ + configmap, + expanded, +}: { + configmap: V1ConfigMap; + expanded?: boolean; +}) => { + const namespace = configmap.metadata?.namespace; + return ( + { + return configmapObject || {}; + }} + > + + + + {configmap.metadata?.name ?? 'unknown object'} + + + + + ConfigMap + + + {namespace && ( + + + + )} + + + ); +}; diff --git a/plugins/kubernetes-react/src/components/ConfigmapsAccordions/index.ts b/plugins/kubernetes-react/src/components/ConfigmapsAccordions/index.ts new file mode 100644 index 0000000000..25a1578a79 --- /dev/null +++ b/plugins/kubernetes-react/src/components/ConfigmapsAccordions/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 './ConfigmapsAccordions.tsx'; diff --git a/plugins/kubernetes-react/src/components/Pods/PodsTable.test.tsx b/plugins/kubernetes-react/src/components/Pods/PodsTable.test.tsx index fb90d17e2b..76136641af 100644 --- a/plugins/kubernetes-react/src/components/Pods/PodsTable.test.tsx +++ b/plugins/kubernetes-react/src/components/Pods/PodsTable.test.tsx @@ -74,7 +74,7 @@ describe('PodsTable', () => { limitTotal: '134217728', }, cpu: { - currentUsage: 0.4966115, + currentUsage: 0.0496, requestTotal: 0.05, limitTotal: 0.05, }, diff --git a/plugins/kubernetes-react/src/utils/pod.test.tsx b/plugins/kubernetes-react/src/utils/pod.test.tsx index 3f82c14f86..a75039ceea 100644 --- a/plugins/kubernetes-react/src/utils/pod.test.tsx +++ b/plugins/kubernetes-react/src/utils/pod.test.tsx @@ -42,7 +42,7 @@ describe('pod', () => { const result = podStatusToCpuUtil({ cpu: { // ~50m - currentUsage: 0.4966115, + currentUsage: 0.0496, // 50m requestTotal: 0.05, // 100m diff --git a/plugins/kubernetes-react/src/utils/pod.tsx b/plugins/kubernetes-react/src/utils/pod.tsx index 97b44d4d24..e033b7c3b8 100644 --- a/plugins/kubernetes-react/src/utils/pod.tsx +++ b/plugins/kubernetes-react/src/utils/pod.tsx @@ -139,22 +139,14 @@ export const currentToDeclaredResourceToPerc = ( export const podStatusToCpuUtil = (podStatus: ClientPodStatus): ReactNode => { const cpuUtil = podStatus.cpu; - let currentUsage: number | string = cpuUtil.currentUsage; - - // current usage number for CPU is a different unit than request/limit total - // this might be a bug in the k8s library - if (typeof cpuUtil.currentUsage === 'number') { - currentUsage = cpuUtil.currentUsage / 10; - } - return ( diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index 627226fee0..62ffb354bf 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/plugin-kubernetes +## 0.12.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.18.2-next.1 + - @backstage/core-plugin-api@1.11.1-next.0 + - @backstage/plugin-permission-react@0.4.37-next.0 + - @backstage/core-compat-api@0.5.3-next.1 + - @backstage/plugin-catalog-react@1.21.2-next.1 + - @backstage/plugin-kubernetes-react@0.5.12-next.1 + - @backstage/frontend-plugin-api@0.12.1-next.1 + - @backstage/plugin-kubernetes-common@0.9.7-next.1 + +## 0.12.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.21.2-next.0 + - @backstage/core-components@0.18.2-next.0 + - @backstage/plugin-kubernetes-common@0.9.7-next.0 + - @backstage/core-compat-api@0.5.3-next.0 + - @backstage/frontend-plugin-api@0.12.1-next.0 + - @backstage/plugin-kubernetes-react@0.5.12-next.0 + - @backstage/catalog-model@1.7.5 + - @backstage/core-plugin-api@1.11.0 + - @backstage/plugin-permission-react@0.4.36 + ## 0.12.11 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index e9da7b47ec..1ae796c1c0 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes", - "version": "0.12.11", + "version": "0.12.12-next.1", "description": "A Backstage plugin that integrates towards Kubernetes", "backstage": { "role": "frontend-plugin", diff --git a/plugins/mcp-actions-backend/CHANGELOG.md b/plugins/mcp-actions-backend/CHANGELOG.md index 8f079b5dd6..b00eb4ed8d 100644 --- a/plugins/mcp-actions-backend/CHANGELOG.md +++ b/plugins/mcp-actions-backend/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-mcp-actions-backend +## 0.1.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.0-next.1 + - @backstage/backend-plugin-api@1.4.4-next.0 + - @backstage/plugin-catalog-node@1.19.1-next.0 + - @backstage/catalog-client@1.12.0 + +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.13.0-next.0 + - @backstage/backend-plugin-api@1.4.3 + - @backstage/catalog-client@1.12.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.2 + - @backstage/plugin-catalog-node@1.19.0 + ## 0.1.3 ### Patch Changes diff --git a/plugins/mcp-actions-backend/package.json b/plugins/mcp-actions-backend/package.json index 4b8bbd3b72..804bb0251a 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.3", + "version": "0.1.4-next.1", "backstage": { "role": "backend-plugin", "pluginId": "mcp-actions", diff --git a/plugins/mui-to-bui/.eslintrc.js b/plugins/mui-to-bui/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/mui-to-bui/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/mui-to-bui/CHANGELOG.md b/plugins/mui-to-bui/CHANGELOG.md new file mode 100644 index 0000000000..14720ff0e7 --- /dev/null +++ b/plugins/mui-to-bui/CHANGELOG.md @@ -0,0 +1,13 @@ +# @backstage/plugin-mui-to-bui + +## 0.2.0-next.0 + +### Minor Changes + +- d5cbdba: This is the first release of the Material UI to Backstage UI migration helper plugin. It adds a new page at `/mui-to-bui` that converts an existing MUI v5 theme into Backstage UI (BUI) CSS variables, with live preview and copy/download. + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.7.2-next.1 + - @backstage/theme@0.6.9-next.0 diff --git a/plugins/mui-to-bui/README.md b/plugins/mui-to-bui/README.md new file mode 100644 index 0000000000..4e11db20b6 --- /dev/null +++ b/plugins/mui-to-bui/README.md @@ -0,0 +1,63 @@ +# mui-to-bui + +## Description + +The Backstage UI Themer helps you convert an existing MUI v5 theme into Backstage UI (BUI) CSS custom properties. It detects installed app themes, generates a complete set of BUI CSS variables for each theme (light and dark), and lets you preview how common BUI components look with your colors and typography. You can copy the generated CSS to the clipboard or download it as a `.css` file, and it works with both the old and new Backstage frontend systems without requiring any backend setup. + +## Installation + +### 1) Add the dependency to your app + +Run this from your Backstage repo root: + +```bash +yarn add --cwd packages/app @backstage/plugin-mui-to-bui +``` + +### 2) Wire it up depending on your frontend system + +#### Old frontend system (legacy `App.tsx` with ``) + +Add a route for the page in your app: + +```tsx +// packages/app/src/App.tsx +import React from 'react'; +import { Route } from 'react-router-dom'; +import { FlatRoutes } from '@backstage/core-app-api'; +import { BuiThemerPage } from '@backstage/plugin-mui-to-bui'; + +export const App = () => ( + + {/* ...your other routes */} + } /> + +); +``` + +#### New frontend system + +If package discovery is enabled in your app, this plugin is picked up automatically after installation — no code changes required. Just navigate to `/mui-to-bui`. + +If you prefer explicit registration (or don't use discovery), register the plugin as a feature. The page route (`/mui-to-bui`) is provided by the plugin. + +```tsx +// packages/app/src/App.tsx (or your app entry where you call createApp) +import React from 'react'; +import { createApp } from '@backstage/frontend-defaults'; +import buiThemerPlugin from '@backstage/plugin-mui-to-bui'; + +const app = createApp({ + features: [ + // ...other features + buiThemerPlugin, + ], +}); + +export default app.createRoot(); +``` + +## Accessing the Themer page + +- Navigate to `/mui-to-bui` in your Backstage app (for example `http://localhost:3000/mui-to-bui`). +- Optional: Add a sidebar/link in your app that points to `/mui-to-bui` if you want a permanent navigation entry. diff --git a/plugins/mui-to-bui/catalog-info.yaml b/plugins/mui-to-bui/catalog-info.yaml new file mode 100644 index 0000000000..9e3605267d --- /dev/null +++ b/plugins/mui-to-bui/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-mui-to-bui + title: '@backstage/plugin-mui-to-bui' +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/mui-to-bui/dev/index.tsx b/plugins/mui-to-bui/dev/index.tsx new file mode 100644 index 0000000000..8837d540c5 --- /dev/null +++ b/plugins/mui-to-bui/dev/index.tsx @@ -0,0 +1,26 @@ +/* + * 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 { createDevApp } from '@backstage/dev-utils'; +import { buiThemerPlugin, BuiThemerPage } from '../src/plugin'; + +createDevApp() + .registerPlugin(buiThemerPlugin) + .addPage({ + element: , + title: 'Root Page', + path: '/mui-to-bui', + }) + .render(); diff --git a/plugins/mui-to-bui/knip-report.md b/plugins/mui-to-bui/knip-report.md new file mode 100644 index 0000000000..2661c35327 --- /dev/null +++ b/plugins/mui-to-bui/knip-report.md @@ -0,0 +1,2 @@ +# Knip report + diff --git a/plugins/mui-to-bui/package.json b/plugins/mui-to-bui/package.json new file mode 100644 index 0000000000..b8cb51b1c8 --- /dev/null +++ b/plugins/mui-to-bui/package.json @@ -0,0 +1,68 @@ +{ + "name": "@backstage/plugin-mui-to-bui", + "version": "0.2.0-next.0", + "backstage": { + "role": "frontend-plugin", + "pluginId": "mui-to-bui", + "pluginPackages": [ + "@backstage/plugin-mui-to-bui" + ] + }, + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/mui-to-bui" + }, + "license": "Apache-2.0", + "sideEffects": false, + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "backstage-cli package build", + "clean": "backstage-cli package clean", + "lint": "backstage-cli package lint", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "start": "backstage-cli package start", + "test": "backstage-cli package test" + }, + "dependencies": { + "@backstage/core-compat-api": "workspace:^", + "@backstage/core-plugin-api": "workspace:^", + "@backstage/frontend-plugin-api": "workspace:^", + "@backstage/theme": "workspace:^", + "@backstage/ui": "workspace:^", + "@mui/material": "^5.12.2", + "@mui/system": "^5.16.14" + }, + "devDependencies": { + "@backstage/cli": "workspace:^", + "@backstage/dev-utils": "workspace:^", + "@backstage/test-utils": "workspace:^", + "@testing-library/jest-dom": "^6.0.0", + "@testing-library/react": "^16.0.0", + "@types/react": "^18.0.0", + "react": "^18.0.2", + "react-dom": "^18.0.2", + "react-router-dom": "^6.3.0" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0", + "react-router-dom": "^6.3.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } +} diff --git a/plugins/mui-to-bui/report.api.md b/plugins/mui-to-bui/report.api.md new file mode 100644 index 0000000000..593130ea86 --- /dev/null +++ b/plugins/mui-to-bui/report.api.md @@ -0,0 +1,66 @@ +## API Report File for "@backstage/plugin-mui-to-bui" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { BackstagePlugin } from '@backstage/core-plugin-api'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { JSX as JSX_2 } from 'react'; +import { JSX as JSX_3 } from 'react/jsx-runtime'; +import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; +import { RouteRef } from '@backstage/frontend-plugin-api'; +import { RouteRef as RouteRef_2 } from '@backstage/core-plugin-api'; + +// @public (undocumented) +export const BuiThemerPage: () => JSX_3.Element; + +// @public (undocumented) +export const buiThemerPlugin: BackstagePlugin< + { + root: RouteRef_2; + }, + {} +>; + +// @public (undocumented) +const _default: OverridableFrontendPlugin< + { + root: RouteRef; + }, + {}, + { + 'page:mui-to-bui': ExtensionDefinition<{ + kind: 'page'; + name: undefined; + config: { + path: string | undefined; + }; + configInput: { + path?: string | undefined; + }; + output: + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >; + inputs: {}; + params: { + defaultPath?: [Error: `Use the 'path' param instead`]; + path: string; + loader: () => Promise; + routeRef?: RouteRef; + }; + }>; + } +>; +export default _default; + +// (No @packageDocumentation comment for this package) +``` diff --git a/plugins/mui-to-bui/src/components/BuiThemerPage/BuiThemePreview.test.tsx b/plugins/mui-to-bui/src/components/BuiThemerPage/BuiThemePreview.test.tsx new file mode 100644 index 0000000000..375f9411a7 --- /dev/null +++ b/plugins/mui-to-bui/src/components/BuiThemerPage/BuiThemePreview.test.tsx @@ -0,0 +1,55 @@ +/* + * 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 { screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { renderInTestApp } from '@backstage/test-utils'; +import { BuiThemePreview } from './BuiThemePreview'; + +describe('BuiThemePreview', () => { + it('renders headings and sample components', async () => { + const { container } = await renderInTestApp( + , + ); + + expect(container.firstChild).toHaveAttribute('data-theme-mode', 'light'); + expect(await screen.findByText('Button Variants')).toBeInTheDocument(); + expect(screen.getByText('Button Variants')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Primary' })).toBeInTheDocument(); + expect( + screen.getByRole('button', { name: 'Secondary' }), + ).toBeInTheDocument(); + expect( + screen.getByRole('button', { name: 'Tertiary' }), + ).toBeInTheDocument(); + expect(screen.getByText('Form Inputs')).toBeInTheDocument(); + expect(screen.getByText('Tag Variants')).toBeInTheDocument(); + expect(screen.getByText('Text Variants')).toBeInTheDocument(); + }); +}); diff --git a/plugins/mui-to-bui/src/components/BuiThemerPage/BuiThemePreview.tsx b/plugins/mui-to-bui/src/components/BuiThemerPage/BuiThemePreview.tsx new file mode 100644 index 0000000000..e59f4e78dd --- /dev/null +++ b/plugins/mui-to-bui/src/components/BuiThemerPage/BuiThemePreview.tsx @@ -0,0 +1,167 @@ +/* + * 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 { + Box, + Button, + Card, + CardBody, + CardHeader, + Checkbox, + Flex, + Radio, + RadioGroup, + Select, + Text, + TextField, + TagGroup, + Tag, +} from '@backstage/ui'; + +interface IsolatedPreviewProps { + mode: 'light' | 'dark'; + styleObject: Record; +} + +export function BuiThemePreview({ mode, styleObject }: IsolatedPreviewProps) { + return ( +
+ + + Button Variants + + + + + + + + + + + + + + + + + + + + Form Inputs + + + +