diff --git a/.changeset/bitter-pants-watch.md b/.changeset/bitter-pants-watch.md new file mode 100644 index 0000000000..3accf45826 --- /dev/null +++ b/.changeset/bitter-pants-watch.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': minor +--- + +We are updating the default size of the Button component in Canon to be small instead of medium. diff --git a/.changeset/brave-facts-pay.md b/.changeset/brave-facts-pay.md new file mode 100644 index 0000000000..1f8e9663bf --- /dev/null +++ b/.changeset/brave-facts-pay.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-confluence-to-markdown': patch +--- + +Migrate actions to new format diff --git a/.changeset/chatty-clowns-dream.md b/.changeset/chatty-clowns-dream.md new file mode 100644 index 0000000000..d601f7a40d --- /dev/null +++ b/.changeset/chatty-clowns-dream.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +table actions header support i18n diff --git a/.changeset/cold-meals-like.md b/.changeset/cold-meals-like.md new file mode 100644 index 0000000000..7feb978337 --- /dev/null +++ b/.changeset/cold-meals-like.md @@ -0,0 +1,5 @@ +--- +'@backstage/catalog-client': patch +--- + +Fixed `CatalogClient` error responses for `refreshEntity` and `addLocation`. diff --git a/.changeset/create-app-1748956288.md b/.changeset/create-app-1748956288.md new file mode 100644 index 0000000000..b50d431d4b --- /dev/null +++ b/.changeset/create-app-1748956288.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Bumped create-app version. diff --git a/.changeset/cuddly-camels-lose.md b/.changeset/cuddly-camels-lose.md new file mode 100644 index 0000000000..23162bfdc5 --- /dev/null +++ b/.changeset/cuddly-camels-lose.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +--- + +Migrate to new action format diff --git a/.changeset/curvy-peaches-relate.md b/.changeset/curvy-peaches-relate.md new file mode 100644 index 0000000000..9fafc77abb --- /dev/null +++ b/.changeset/curvy-peaches-relate.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-azure': patch +--- + +Migrating `azure` actions to using the new `zod` schema format diff --git a/.changeset/dull-buttons-press.md b/.changeset/dull-buttons-press.md new file mode 100644 index 0000000000..1c8f8fc13c --- /dev/null +++ b/.changeset/dull-buttons-press.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch +--- + +Migrate `bitbucket-cloud` to new actions format diff --git a/.changeset/dull-cars-shout.md b/.changeset/dull-cars-shout.md new file mode 100644 index 0000000000..be213a167c --- /dev/null +++ b/.changeset/dull-cars-shout.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-github': patch +--- + +Migrate to using new actions diff --git a/.changeset/eight-pigs-post.md b/.changeset/eight-pigs-post.md new file mode 100644 index 0000000000..8e50cc7707 --- /dev/null +++ b/.changeset/eight-pigs-post.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': minor +--- + +We set the default size for IconButton in Canon to be small instead of medium. diff --git a/.changeset/eight-planets-see.md b/.changeset/eight-planets-see.md new file mode 100644 index 0000000000..20adcac7ef --- /dev/null +++ b/.changeset/eight-planets-see.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +render details for composite property schemas diff --git a/.changeset/eighty-showers-hide.md b/.changeset/eighty-showers-hide.md new file mode 100644 index 0000000000..0871fea4bb --- /dev/null +++ b/.changeset/eighty-showers-hide.md @@ -0,0 +1,9 @@ +--- +'@backstage/plugin-scaffolder-node-test-utils': minor +--- + +**BREAKING CHANGES** + +Because of the removal of the `logStream` property to the `ActionsContext` this has been removed from the `createMockActionContext` method. + +You can remove this as it's no longer supported in the scaffolder actions. diff --git a/.changeset/evil-cooks-watch.md b/.changeset/evil-cooks-watch.md new file mode 100644 index 0000000000..0982f24634 --- /dev/null +++ b/.changeset/evil-cooks-watch.md @@ -0,0 +1,21 @@ +--- +'@backstage/plugin-catalog-unprocessed-entities': patch +'@backstage/plugin-app-visualizer': patch +'@backstage/plugin-catalog-import': patch +'@backstage/plugin-catalog-graph': patch +'@backstage/plugin-notifications': patch +'@backstage/plugin-user-settings': patch +'@backstage/plugin-kubernetes': patch +'@backstage/plugin-scaffolder': patch +'@backstage/plugin-api-docs': patch +'@backstage/plugin-devtools': patch +'@backstage/plugin-techdocs': patch +'@backstage/plugin-catalog': patch +'@backstage/plugin-signals': patch +'@backstage/plugin-search': patch +'@backstage/plugin-home': patch +'@backstage/plugin-app': patch +'@backstage/plugin-org': patch +--- + +Added the `info.packageJson` option to the plugin instance for the new frontend system. diff --git a/.changeset/fair-doors-bathe.md b/.changeset/fair-doors-bathe.md new file mode 100644 index 0000000000..bbb5f238ee --- /dev/null +++ b/.changeset/fair-doors-bathe.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': patch +--- + +Fixed a bug where linting would fail with the generated clients when defining top-level `enum` schema values. diff --git a/.changeset/famous-streets-stay.md b/.changeset/famous-streets-stay.md new file mode 100644 index 0000000000..d517b889c5 --- /dev/null +++ b/.changeset/famous-streets-stay.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-node': patch +--- + +Use `LoggerService` instead of `Logger`. This is a non-breaking change, as the `LoggerService` is a subset of the `Logger` interface. diff --git a/.changeset/few-streets-accept.md b/.changeset/few-streets-accept.md new file mode 100644 index 0000000000..4b82cc03e4 --- /dev/null +++ b/.changeset/few-streets-accept.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-bitbucket-server': patch +--- + +Migrate the actions to the new format diff --git a/.changeset/fix-cli-config-docs-feedback.md b/.changeset/fix-cli-config-docs-feedback.md new file mode 100644 index 0000000000..1e7298bafa --- /dev/null +++ b/.changeset/fix-cli-config-docs-feedback.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Added user feedback when opening config docs in browser. The command now clearly indicates what it's doing and provides fallback instructions if the browser fails to open. diff --git a/.changeset/fresh-brooms-follow.md b/.changeset/fresh-brooms-follow.md new file mode 100644 index 0000000000..f2858779bf --- /dev/null +++ b/.changeset/fresh-brooms-follow.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +The `BACKSTAGE_CLI_EXPERIMENTAL_BUILD_CACHE` flag has been removed. Existing users are encouraged to switch to `EXPERIMENTAL_RSPACK` instead. diff --git a/.changeset/fresh-otters-say.md b/.changeset/fresh-otters-say.md new file mode 100644 index 0000000000..ed91c9fb8d --- /dev/null +++ b/.changeset/fresh-otters-say.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Migrating to latest action format diff --git a/.changeset/green-schools-live.md b/.changeset/green-schools-live.md new file mode 100644 index 0000000000..9863021a28 --- /dev/null +++ b/.changeset/green-schools-live.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-yeoman': patch +--- + +Migrate to new actions format diff --git a/.changeset/grumpy-dryers-act.md b/.changeset/grumpy-dryers-act.md new file mode 100644 index 0000000000..caaeac5fa8 --- /dev/null +++ b/.changeset/grumpy-dryers-act.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': patch +--- + +Add missing highlight language for the `package-docs` command. diff --git a/.changeset/honest-moles-bet.md b/.changeset/honest-moles-bet.md new file mode 100644 index 0000000000..22e0cb4533 --- /dev/null +++ b/.changeset/honest-moles-bet.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-scaffolder-backend-module-bitbucket-server': patch +'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch +'@backstage/plugin-scaffolder-node': patch +--- + +Update pull request creation filter to include .gitignore files in the created pull request diff --git a/.changeset/khaki-horses-chew.md b/.changeset/khaki-horses-chew.md new file mode 100644 index 0000000000..b9f5dfda5e --- /dev/null +++ b/.changeset/khaki-horses-chew.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-techdocs-module-addons-contrib': patch +'@backstage/plugin-techdocs': patch +--- + +Improved Keyboard accessibility in techdocs. diff --git a/.changeset/khaki-swans-invite.md b/.changeset/khaki-swans-invite.md new file mode 100644 index 0000000000..4e768a1978 --- /dev/null +++ b/.changeset/khaki-swans-invite.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-notifications-backend-module-email': patch +'@backstage/plugin-notifications-backend': patch +--- + +Internal changes to switch to the non-alpha `catalogServiceRef` diff --git a/.changeset/large-baboons-prove.md b/.changeset/large-baboons-prove.md new file mode 100644 index 0000000000..072d57de4c --- /dev/null +++ b/.changeset/large-baboons-prove.md @@ -0,0 +1,9 @@ +--- +'@backstage/plugin-catalog': minor +--- + +Add support to customize the about card icon links via `EntityIconLinkBlueprint` and provide a default catalog view catalog source, launch scaffolder template and read techdocs docs icon links extensions. + +**BREAKING ALPHA** + +The `Scaffolder` launch template and `TechDocs` read documentation icons have been extracted from the default `Catalog` about card links and are now provided respectively by the `Scaffolder` and `TechDocs` plugins in the new frontend system. It means that they will not be available unless you install the `TechDocs` and `Scaffolder` plugins. Also If you are using translation for these icon link titles other than the default, you should now translate them using the scaffolder translation reference or the TechDocs translation reference (the translation keys are still the same, `aboutCard.viewTechdocs` and `aboutCard.launchTemplate`). diff --git a/.changeset/late-buttons-strive.md b/.changeset/late-buttons-strive.md new file mode 100644 index 0000000000..23e1045ba0 --- /dev/null +++ b/.changeset/late-buttons-strive.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-org': patch +--- + +Org plugin support i18n diff --git a/.changeset/lazy-rice-dream.md b/.changeset/lazy-rice-dream.md new file mode 100644 index 0000000000..fe91f85b2e --- /dev/null +++ b/.changeset/lazy-rice-dream.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gerrit': patch +--- + +Migrating to use new format for actions diff --git a/.changeset/long-suits-obey.md b/.changeset/long-suits-obey.md new file mode 100644 index 0000000000..996bba6026 --- /dev/null +++ b/.changeset/long-suits-obey.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-user-settings': patch +--- + +Add some translation to user-setting plugin diff --git a/.changeset/loose-ants-learn.md b/.changeset/loose-ants-learn.md new file mode 100644 index 0000000000..29f357966c --- /dev/null +++ b/.changeset/loose-ants-learn.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-techdocs': minor +--- + +**New Frontend System Only:** +The `TechDocs` plugin is now responsible for providing an entity icon link extension to read documentation from the catalog entity page. diff --git a/.changeset/major-heads-scream.md b/.changeset/major-heads-scream.md new file mode 100644 index 0000000000..24caa6e4bb --- /dev/null +++ b/.changeset/major-heads-scream.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-gitlab': minor +--- + +**BREAKING CHANGE**: User and Group discovery will default to ingesting all users in sub groups that belong to the specified root group in config. Disable by setting `restrictUsersToGroup: true` in app-config under your module settings. diff --git a/.changeset/mean-tigers-rush.md b/.changeset/mean-tigers-rush.md new file mode 100644 index 0000000000..e15b01cedc --- /dev/null +++ b/.changeset/mean-tigers-rush.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Switched to using the `ModuleFederationPlugin` from `@module-federation/enhanced/rspack` for Rspack, rather than the built-in one. diff --git a/.changeset/mighty-windows-boil.md b/.changeset/mighty-windows-boil.md new file mode 100644 index 0000000000..52eb024db1 --- /dev/null +++ b/.changeset/mighty-windows-boil.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-sentry': patch +--- + +Migrate to new actions format diff --git a/.changeset/mira-flygande-spoons.md b/.changeset/mira-flygande-spoons.md new file mode 100644 index 0000000000..c4c50f117a --- /dev/null +++ b/.changeset/mira-flygande-spoons.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +You can now specify an optional value when applying the `HAS_LABEL` permission rule, similar to the `HAS_ANNOTATION` permission rule. diff --git a/.changeset/new-beers-sell.md b/.changeset/new-beers-sell.md new file mode 100644 index 0000000000..fd7407007d --- /dev/null +++ b/.changeset/new-beers-sell.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-scaffolder-backend-module-notifications': patch +'@backstage/plugin-notifications-backend': patch +'@backstage/plugin-notifications-common': patch +'@backstage/plugin-notifications': patch +--- + +Adds ability for user to turn on/off notifications for specific topics within an origin. diff --git a/.changeset/olive-buses-serve.md b/.changeset/olive-buses-serve.md new file mode 100644 index 0000000000..d4e1798360 --- /dev/null +++ b/.changeset/olive-buses-serve.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend-module-techdocs': patch +--- + +Exports the default document collator for use in document transformation during search indexing. diff --git a/.changeset/open-bottles-attack.md b/.changeset/open-bottles-attack.md new file mode 100644 index 0000000000..e57607b55c --- /dev/null +++ b/.changeset/open-bottles-attack.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-notifications': patch +--- + +Removed `octokit` dependency as it was not being used diff --git a/.changeset/open-eyes-learn.md b/.changeset/open-eyes-learn.md new file mode 100644 index 0000000000..3a0299aafb --- /dev/null +++ b/.changeset/open-eyes-learn.md @@ -0,0 +1,49 @@ +--- +'@backstage/plugin-catalog-react': minor +--- + +Introduces a new `EntityIconLinkBlueprint` that customizes the `About` card icon links on the `Catalog` entity page. + +The blueprint currently accepts a `useProps` hook as `param` and this function returns the following props that will be passed to the icon link component: + +| Name | Description | Type | Default Value | +| ---------- | --------------------------------------------------- | ------------- | ------------- | +| `icon` | The icon to display. | `JSX.Element` | N/A | +| `label` | The label for the element. | `string` | N/A | +| `title` | The title for the element. | `string` | N/A | +| `disabled` | Whether the element is disabled. | `boolean` | `false` | +| `href` | The URL to navigate to when the element is clicked. | `string` | N/A | +| `onClick` | A function to call when the element is clicked. | `() => void` | N/A | + +Here is an usage example: + +```tsx +import { EntityIconLinkBlueprint } from '@backstage/plugin-catalog-react/alpha'; +//... + +EntityIconLinkBlueprint.make({ + name: 'my-icon-link', + params: { + useProps() { + const { t } = useTranslationRef(myIconLinkTranslationRef); + return { + label: t('myIconLink.label'), + icon: , + href: '/my-plugin', + }; + }, + }, +}); +``` + +Additionally, the `app-config.yaml` file allows you to override some of the default icon link parameters, including `label` and `title` values. Here's how to set them: + +```yaml +app: + extensions: + - entity-icon-link:my-plugin/my-icon-link: + config: + label: 'My Custom Icon Link label' +``` + +Finally, you can disable all links if you want to hide the About card header completely (useful, for example, when links are displayed on separate cards). The header is hidden when no icon links extensions are enabled. diff --git a/.changeset/orange-masks-decide.md b/.changeset/orange-masks-decide.md new file mode 100644 index 0000000000..0ddb7a7e48 --- /dev/null +++ b/.changeset/orange-masks-decide.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-rails': patch +--- + +Migrate to new actions format diff --git a/.changeset/petite-candies-tan.md b/.changeset/petite-candies-tan.md new file mode 100644 index 0000000000..6ec5c9c813 --- /dev/null +++ b/.changeset/petite-candies-tan.md @@ -0,0 +1,29 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Added a new optional `info` option to `createFrontendPlugin` that lets you provide a loaders for different sources of metadata information about the plugin. + +There are two available loaders. The first one is `info.packageJson`, which can be used to point to a `package.json` file for the plugin. This is recommended for any plugin that is defined within its own package, especially all plugins that are published to a package registry. Typical usage looks like this: + +```ts +export default createFrontendPlugin({ + pluginId: '...', + info: { + packageJson: () => import('../package.json'), + }, +}); +``` + +The second loader is `info.manifest`, which can be used to point to an opaque plugin manifest. This **MUST ONLY** be used by plugins that are intended for use within a single organization. Plugins that are published to an open package registry should **NOT** use this loader. The loader is useful for adding additional internal metadata associated with the plugin, and it is up to the Backstage app to decide how these manifests are parsed and used. The default manifest parser in an app created with `createApp` from `@backstage/frontend-defaults` is able to parse the default `catalog-info.yaml` format and built-in fields such as `spec.owner`. + +Typical usage looks like this: + +```ts +export default createFrontendPlugin({ + pluginId: '...', + info: { + manifest: () => import('../catalog-info.yaml'), + }, +}); +``` diff --git a/.changeset/pre.json b/.changeset/pre.json index c09d56c9d4..2f88c82873 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -201,5 +201,55 @@ "@backstage/plugin-user-settings-backend": "0.3.2", "@backstage/plugin-user-settings-common": "0.0.1" }, - "changesets": [] + "changesets": [ + "bitter-pants-watch", + "brave-facts-pay", + "cold-meals-like", + "create-app-1748956288", + "curvy-peaches-relate", + "dull-buttons-press", + "eight-pigs-post", + "eight-planets-see", + "evil-cooks-watch", + "famous-streets-stay", + "few-streets-accept", + "fresh-brooms-follow", + "good-paths-act", + "grumpy-dryers-act", + "honest-moles-bet", + "khaki-horses-chew", + "late-buttons-strive", + "lazy-rice-dream", + "long-suits-obey", + "mean-tigers-rush", + "new-beers-sell", + "olive-buses-serve", + "petite-candies-tan", + "puny-pillows-wave", + "red-vans-march", + "ripe-squids-sink", + "salty-worlds-float", + "scaffolder-breakages-1", + "scaffolder-breakages-2", + "scaffolder-breakages-3", + "scaffolder-deprecations-1", + "seven-beans-appear", + "shiny-bags-bet", + "six-mangos-yell", + "slow-impalas-wear", + "small-dots-march", + "smooth-eels-think", + "spotty-cases-show", + "stupid-flies-speak", + "stupid-pens-serve", + "sweet-ducks-wait", + "sweet-squids-dig", + "three-boats-sort", + "tough-heads-knock", + "true-tools-play", + "warm-taxes-show", + "wet-bars-report", + "wet-bars-reporting", + "yummy-donuts-peel" + ] } diff --git a/.changeset/puny-pillows-wave.md b/.changeset/puny-pillows-wave.md new file mode 100644 index 0000000000..813e603007 --- /dev/null +++ b/.changeset/puny-pillows-wave.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Added a new `useAppNode` hook, which can be used to get a reference to the `AppNode` from by the closest `ExtensionBoundary`. diff --git a/.changeset/rare-rules-move.md b/.changeset/rare-rules-move.md new file mode 100644 index 0000000000..6abc778941 --- /dev/null +++ b/.changeset/rare-rules-move.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-scaffolder': minor +--- + +**New Frontend System Only:** +The `Scaffolder` plugin is now responsible for providing an entity icon link extension to launch templates from the catalog entity page. diff --git a/.changeset/red-vans-march.md b/.changeset/red-vans-march.md new file mode 100644 index 0000000000..a168e89884 --- /dev/null +++ b/.changeset/red-vans-march.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gitea': patch +--- + +Migrate to new actions format diff --git a/.changeset/ripe-squids-sink.md b/.changeset/ripe-squids-sink.md new file mode 100644 index 0000000000..27d5d043b6 --- /dev/null +++ b/.changeset/ripe-squids-sink.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +The experimental `FORCE_REACT_DEVELOPMENT` flag has been removed. diff --git a/.changeset/salty-worlds-float.md b/.changeset/salty-worlds-float.md new file mode 100644 index 0000000000..a21d0b2e7c --- /dev/null +++ b/.changeset/salty-worlds-float.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': minor +--- + +Add .cache directory to shipped gitignore. diff --git a/.changeset/scaffolder-breakages-1.md b/.changeset/scaffolder-breakages-1.md new file mode 100644 index 0000000000..53a7ba9d6a --- /dev/null +++ b/.changeset/scaffolder-breakages-1.md @@ -0,0 +1,27 @@ +--- +'@backstage/plugin-scaffolder-backend': major +--- + +**BREAKING CHANGES** + +Removal of deprecated re-exports from module packages. + +The following functions have been re-exported from the `scaffolder-backend` plugin for quite some time, and now it's time to clean them up. They've been moved as follows: + +- `createPublishAzureAction` should be imported from `@backstage/plugin-scaffolder-backend-module-azure` instead. + +- `createPublishBitbucketCloudAction` should be imported from `@backstage/plugin-scaffolder-backend-module-bitbucket-cloud` instead. + +- `createPublishBitbucketServerAction` and `createPublishBitbucketServerPullRequestAction` can be imported from `@backstage/plugin-scaffolder-backend-module-bitbucket-server` instead. + +- `createPublishBitbucketAction` should be imported from `@backstage/plugin-scaffolder-backend-module-bitbucket` instead. + +- `createPublishGerritAction` and `createPublishGerritReviewAction` can be imported from `@backstage/plugin-scaffolder-backend-module-gerrit` instead. + +- `createGithubActionsDispatchAction`, `createGithubDeployKeyAction`, `createGithubEnvironmentAction`, `createGithubIssuesLabelAction`, `CreateGithubPullRequestActionOptions`, `createGithubRepoCreateAction`, `createGithubRepoPushAction`, `createGithubWebhookAction`, and `createPublishGithubAction` can be imported from `@backstage/plugin-scaffolder-backend-module-github` instead. + +- `createPublishGitlabAction` should be imported from `@backstage/plugin-scaffolder-backend-module-gitlab` instead. + +- `ActionContext`. `createTemplateAction`, `executeShellCommand`, `ExecuteShellCommandOptions`, `fetchContents`, `TaskSecrets`, and `TemplateAction` should be imported from `@backstage/plugin-scaffolder-node` instead. + +- `ScaffolderEntitiesProcessor` should be imported from `@backstage/plugin-catalog-backend-module-scaffolder-entity-model` instead. diff --git a/.changeset/scaffolder-breakages-2.md b/.changeset/scaffolder-breakages-2.md new file mode 100644 index 0000000000..77ffe5fc42 --- /dev/null +++ b/.changeset/scaffolder-breakages-2.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-scaffolder-backend': major +--- + +**BREAKING ALPHA**: The `/alpha` export no longer exports the plugin. Please use `import('@backstage/plugin-scaffolder-backend')` instead as this has been removed. + +**BREAKING CHANGES**: The old `createRouter` function which was used in the old backend system has been removed along with the `RouterOptions` type. diff --git a/.changeset/scaffolder-breakages-3.md b/.changeset/scaffolder-breakages-3.md new file mode 100644 index 0000000000..f6dca6416f --- /dev/null +++ b/.changeset/scaffolder-breakages-3.md @@ -0,0 +1,11 @@ +--- +'@backstage/plugin-scaffolder-backend': major +--- + +**BREAKING CHANGES** + +The following functions have been re-exported from the `scaffolder-backend` plugin for quite some time, and now it's time to clean them up. They've been moved as follows: + +- `SerializedTask`, `SerializedTaskEvent`, `TaskBroker`, `TaskBrokerDispatchOptions`, `TaskBrokerDispatchResult`, `TaskCompletionState`, `TaskContext`, `TaskEventType`, `TaskStatus`, `TemplateFilter`, and `TemplateGlobal` should be imported from `@backstage/plugin-scaffolder-node` instead. + +- The deprecated `copyWithoutRender` option has been removed from `fetch:template` action. You should rename the option to `copyWithoutTemplating` instead. diff --git a/.changeset/scaffolder-breakages-4.md b/.changeset/scaffolder-breakages-4.md new file mode 100644 index 0000000000..e9f0c81256 --- /dev/null +++ b/.changeset/scaffolder-breakages-4.md @@ -0,0 +1,60 @@ +--- +'@backstage/plugin-scaffolder-node': minor +--- + +**BREAKING CHANGES** + +The legacy methods to define `createTemplateActions` have been replaced with the new native `zod` approaches for defining input and output schemas. + +You can migrate actions that look like the following with the below examples: + +```ts +// really old legacy json schema +createTemplateAction<{ repoUrl: string }, { repoOutput: string }>({ + id: 'test', + schema: { + input: { + type: 'object' + required: ['repoUrl'] + properties: { + repoUrl: { + type: 'string', + description: 'repository url description' + } + } + } + } +}); + +// old zod method +createTemplateAction({ + id: 'test' + schema: { + input: { + repoUrl: z.string({ description: 'repository url description' }) + } + } +}) + +// new method: +createTemplateAction({ + id: 'test', + schema: { + input: { + repoUrl: z => z.string({ description: 'repository url description' }) + } + } +}) + +// or for more complex zod types like unions +createTemplateAction({ + id: 'test', + schema: { + input: z => z.object({ + repoUrl: z.string({ description: 'repository url description' }) + }) + } +}) +``` + +This breaking change also means that `logStream` has been removed entirely from `ActionsContext`, and that the `logger` is now just a `LoggerService` implementation instead. There is no replacement for the `logStream`, if you wish to still keep using a `logStream` we recommend that you create your own stream that writes to `ctx.logger` instead. diff --git a/.changeset/scaffolder-breakages-5.md b/.changeset/scaffolder-breakages-5.md new file mode 100644 index 0000000000..8047ec3dc3 --- /dev/null +++ b/.changeset/scaffolder-breakages-5.md @@ -0,0 +1,36 @@ +--- +'@backstage/plugin-scaffolder-backend-module-github': minor +--- + +**BREAKING CHANGES** + +The `createGithubEnvironmentAction` action no longer requires an `AuthService`, and now accepts a `CatalogService` instead of `CatalogClient`. + +Unless you're providing your own override action to the default, this should be a non-breaking change. + +You can migrate using the following if you're getting typescript errors: + +```ts +import { catalogServiceRef } from '@backstage/plugin-catalog-node'; +import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha'; + +export const myModule = createBackendModule({ + pluginId: 'scaffolder', + moduleId: 'test', + register({ registerInit }) { + registerInit({ + deps: { + scaffolder: scaffolderActionsExtensionPoint, + catalog: catalogServiceRef, + }, + async init({ scaffolder, catalog }) { + scaffolder.addActions( + createGithubEnvironmentAction({ + catalog, + }), + ); + }, + }); + }, +}); +``` diff --git a/.changeset/scaffolder-breakages-6.md b/.changeset/scaffolder-breakages-6.md new file mode 100644 index 0000000000..74ff76c27d --- /dev/null +++ b/.changeset/scaffolder-breakages-6.md @@ -0,0 +1,42 @@ +--- +'@backstage/plugin-scaffolder-backend': major +--- + +**BREAKING CHANGES** + +- The `createBuiltinActions` method has been removed, as this should no longer be needed with the new backend system route, and was only useful when passing the default list of actions again in the old backend system. You should be able to rely on the default behaviour of the new backend system which is to merge the actions. + +- The `createCatalogRegisterAction` and `createFetchCatalogEntityAction` actions no longer require an `AuthService`, and now accepts a `CatalogService` instead of `CatalogClient`. + +Unless you're providing your own override action to the default, this should be a non-breaking change. + +You can migrate using the following if you're getting typescript errors: + +```ts +import { catalogServiceRef } from '@backstage/plugin-catalog-node'; +import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha'; + +export const myModule = createBackendModule({ + pluginId: 'scaffolder', + moduleId: 'test', + register({ registerInit }) { + registerInit({ + deps: { + scaffolder: scaffolderActionsExtensionPoint, + catalog: catalogServiceRef, + }, + async init({ scaffolder, catalog }) { + scaffolder.addActions( + createCatalogRegisterAction({ + catalog, + }), + createFetchCatalogEntityAction({ + catalog, + integrations, + }), + ); + }, + }); + }, +}); +``` diff --git a/.changeset/scaffolder-deprecations-1.md b/.changeset/scaffolder-deprecations-1.md new file mode 100644 index 0000000000..7d87013536 --- /dev/null +++ b/.changeset/scaffolder-deprecations-1.md @@ -0,0 +1,20 @@ +--- +'@backstage/plugin-scaffolder-backend': minor +--- + +**DEPRECATIONS** + +The following types and implementations have been deprecated, either because they're no longer relevant, or because upcoming changes to the `scaffolder-backend` after `2.0.0` will influence the changes to these API surfaces. + +- `CreateWorkerOptions` +- `DatabaseTaskStore` +- `DatabaseTaskStoreOptions` +- `TaskManager` +- `TaskStoreCreateTaskOptions` +- `TaskStoreCreateTaskResult` +- `TaskStoreEmitOptions` +- `TaskStoreListEventsOptions` +- `TaskStoreRecoverTaskOptions` +- `TaskStoreShutDownTaskOptions` + +There is no current path off deprecation, these types are going to be removed and rethought with a better way to define workers in the new backend system. diff --git a/.changeset/seven-beans-appear.md b/.changeset/seven-beans-appear.md new file mode 100644 index 0000000000..82fe9f9889 --- /dev/null +++ b/.changeset/seven-beans-appear.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-gitlab': patch +--- + +Implement retry for GitLab API calls to handle rate limiting diff --git a/.changeset/shiny-bags-bet.md b/.changeset/shiny-bags-bet.md new file mode 100644 index 0000000000..0d9f2c0f30 --- /dev/null +++ b/.changeset/shiny-bags-bet.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-bitbucket': patch +--- + +Migrating `bitbucket` actions to use the new `zod` format diff --git a/.changeset/six-mangos-yell.md b/.changeset/six-mangos-yell.md new file mode 100644 index 0000000000..65ddae86f8 --- /dev/null +++ b/.changeset/six-mangos-yell.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': minor +--- + +Add support for caching the per-package output from the `package-docs` command. diff --git a/.changeset/slimy-cups-shake.md b/.changeset/slimy-cups-shake.md new file mode 100644 index 0000000000..164b741499 --- /dev/null +++ b/.changeset/slimy-cups-shake.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-import': patch +--- + +Catalog import plugin full support i18n diff --git a/.changeset/slow-impalas-wear.md b/.changeset/slow-impalas-wear.md new file mode 100644 index 0000000000..b5151a6ed4 --- /dev/null +++ b/.changeset/slow-impalas-wear.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Only use the caching Jest module loader for frontend packages in order to avoid breaking real ESM module imports. diff --git a/.changeset/small-dots-march.md b/.changeset/small-dots-march.md new file mode 100644 index 0000000000..0796ef68bb --- /dev/null +++ b/.changeset/small-dots-march.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +--- + +Support merge request labels in publish:gitlab:merge-request diff --git a/.changeset/smooth-eels-think.md b/.changeset/smooth-eels-think.md new file mode 100644 index 0000000000..c5e2fe0c85 --- /dev/null +++ b/.changeset/smooth-eels-think.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-techdocs': minor +'@backstage/plugin-techdocs-react': minor +'@backstage/plugin-catalog': minor +'@backstage/plugin-techdocs-common': patch +--- + +Introduced `backstage.io/techdocs-entity-path` annotation which allows deep linking into another entities TechDocs in conjunction with `backstage.io/techdocs-entity`. diff --git a/.changeset/some-llamas-go.md b/.changeset/some-llamas-go.md new file mode 100644 index 0000000000..d07b6a6c75 --- /dev/null +++ b/.changeset/some-llamas-go.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-bitbucket-cloud': minor +--- + +**BREAKING**: `BitbucketCloudEntityProvider` now accepts a `CatalogService` instead of a `CatalogApi`. diff --git a/.changeset/spotty-cases-show.md b/.changeset/spotty-cases-show.md new file mode 100644 index 0000000000..3e9a607733 --- /dev/null +++ b/.changeset/spotty-cases-show.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-defaults': patch +--- + +Forwarded the new `pluginInfoResolver` option for `createApp`. diff --git a/.changeset/strong-trams-joke.md b/.changeset/strong-trams-joke.md new file mode 100644 index 0000000000..8b6a831d7d --- /dev/null +++ b/.changeset/strong-trams-joke.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Updating the scaffolder action boilerplate to use new `zod` schema diff --git a/.changeset/stupid-flies-speak.md b/.changeset/stupid-flies-speak.md new file mode 100644 index 0000000000..5156a5b8b6 --- /dev/null +++ b/.changeset/stupid-flies-speak.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': patch +--- + +Implemented support for the `plugin.info()` method in specialized apps with a default resolved for `package.json` and `catalog-info.yaml`. The default resolution logic can be overridden via the `pluginInfoResolver` option to `createSpecializedApp`, and plugin-specific overrides can be applied via the new `app.pluginOverrides` key in static configuration. diff --git a/.changeset/stupid-pens-serve.md b/.changeset/stupid-pens-serve.md new file mode 100644 index 0000000000..f7fe733254 --- /dev/null +++ b/.changeset/stupid-pens-serve.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Internal refactor to combine alpha `build` and `start` modules. diff --git a/.changeset/sweet-ducks-wait.md b/.changeset/sweet-ducks-wait.md new file mode 100644 index 0000000000..4792fafe84 --- /dev/null +++ b/.changeset/sweet-ducks-wait.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-test-utils': minor +--- + +Added mock implementations for `ActionsService` and `ActionsRegistryService` diff --git a/.changeset/sweet-squids-dig.md b/.changeset/sweet-squids-dig.md new file mode 100644 index 0000000000..78a388ec52 --- /dev/null +++ b/.changeset/sweet-squids-dig.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch +--- + +Migrate using new actions format diff --git a/.changeset/three-boats-sort.md b/.changeset/three-boats-sort.md new file mode 100644 index 0000000000..180a4e236c --- /dev/null +++ b/.changeset/three-boats-sort.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +fix: merge eslint reports when using json format diff --git a/.changeset/tough-heads-knock.md b/.changeset/tough-heads-knock.md new file mode 100644 index 0000000000..da2e2c1163 --- /dev/null +++ b/.changeset/tough-heads-knock.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Update `refresh_state_references.id` to be a big int diff --git a/.changeset/true-tools-play.md b/.changeset/true-tools-play.md new file mode 100644 index 0000000000..4d1ae4d4e8 --- /dev/null +++ b/.changeset/true-tools-play.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch +--- + +Fix `bitbucketCloudBranchRestrictions` API calls to accept null to prevent 400 errors for some branch restriction kinds defined. diff --git a/.changeset/warm-hornets-unite.md b/.changeset/warm-hornets-unite.md new file mode 100644 index 0000000000..65a56233cb --- /dev/null +++ b/.changeset/warm-hornets-unite.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-react': patch +--- + +SearchBar clear button support i18n diff --git a/.changeset/wet-bars-report.md b/.changeset/wet-bars-report.md new file mode 100644 index 0000000000..d556f6cc61 --- /dev/null +++ b/.changeset/wet-bars-report.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-plugin-api': minor +--- + +Added `coreServices.actionsRegistry` and `coreServices.actions` to allow registration of distributed actions from plugins, and the ability to invoke these actions diff --git a/.changeset/wet-bars-reporting.md b/.changeset/wet-bars-reporting.md new file mode 100644 index 0000000000..7dc66cb280 --- /dev/null +++ b/.changeset/wet-bars-reporting.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +Added some default implementations for the `ActionsService` and `ActionsRegistryService` that allow registration of actions for a particular plugin. diff --git a/.changeset/yummy-donuts-peel.md b/.changeset/yummy-donuts-peel.md new file mode 100644 index 0000000000..6c66573025 --- /dev/null +++ b/.changeset/yummy-donuts-peel.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': patch +--- + +Remove leftover console.log from Container component. diff --git a/.github/workflows/deploy_microsite.yml b/.github/workflows/deploy_microsite.yml index 5045e0c96f..67fcc5d709 100644 --- a/.github/workflows/deploy_microsite.yml +++ b/.github/workflows/deploy_microsite.yml @@ -80,6 +80,34 @@ jobs: if-no-files-found: error retention-days: 1 + # Use the lower-level cache actions for the success cache, so that we can store the cache even on failed builds + - name: restore package-docs cache + uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4 + with: + path: .cache/package-docs + key: ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-stable-${{ github.run_id }} + restore-keys: | + ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-stable- + + - name: build API reference (beta) + run: yarn backstage-repo-tools package-docs + + - name: upload API reference (beta) + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: stable-reference-beta + path: type-docs/ + if-no-files-found: error + retention-days: 1 + + # Always save success cache even if there were failures, that way it can be used in re-triggered builds + - name: save package-docs cache + uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4 + if: always() + with: + path: .cache/package-docs + key: ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-stable-${{ github.run_id }} + - name: microsite yarn install run: yarn install --immutable working-directory: microsite @@ -136,6 +164,34 @@ jobs: if-no-files-found: error retention-days: 1 + # Use the lower-level cache actions for the success cache, so that we can store the cache even on failed builds + - name: restore package-docs cache + uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4 + with: + path: .cache/package-docs + key: ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-${{ github.run_id }} + restore-keys: | + ${{ runner.os }}-v${{ matrix.node-version }}-package-docs- + + - name: build API reference (beta) + run: yarn backstage-repo-tools package-docs + + - name: upload API reference (beta) + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: next-reference-beta + path: type-docs/ + if-no-files-found: error + retention-days: 1 + + # Always save success cache even if there were failures, that way it can be used in re-triggered builds + - name: save package-docs cache + uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4 + if: always() + with: + path: .cache/package-docs + key: ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-${{ github.run_id }} + # Also build and upload storybook - name: storybook yarn install run: yarn install --immutable @@ -265,8 +321,18 @@ jobs: name: storybook path: microsite/build/storybook + - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + with: + name: stable-reference-beta + path: microsite/build/api/stable/ + + - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + with: + name: next-reference-beta + path: microsite/build/api/next/ + - name: Check the build output - run: ls microsite/build && ls microsite/build/storybook + run: ls microsite/build && ls microsite/build/storybook && ls microsite/build/api/stable && ls microsite/build/api/next - name: Deploy both microsite and storybook to gh-pages uses: JamesIves/github-pages-deploy-action@6c2d9db40f9296374acc17b90404b6e8864128c8 # v4.7.3 diff --git a/.github/workflows/verify_microsite.yml b/.github/workflows/verify_microsite.yml index c22648bb8c..df68cff2cf 100644 --- a/.github/workflows/verify_microsite.yml +++ b/.github/workflows/verify_microsite.yml @@ -85,6 +85,32 @@ jobs: if-no-files-found: error retention-days: 1 + # Use the lower-level cache actions for the success cache, so that we can store the cache even on failed builds + - name: restore package-docs cache + uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4 + with: + path: .cache/package-docs + key: ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-stable-${{ github.run_id }} + restore-keys: | + ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-stable- + + - name: build API reference (beta) + run: yarn backstage-repo-tools package-docs + + - name: upload API reference (beta) + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: stable-reference-beta + path: type-docs/ + + # Always save success cache even if there were failures, that way it can be used in re-triggered builds + - name: save package-docs cache + uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4 + if: always() + with: + path: .cache/package-docs + key: ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-stable-${{ github.run_id }} + - name: microsite yarn install run: yarn install --immutable working-directory: microsite @@ -140,6 +166,32 @@ jobs: if-no-files-found: error retention-days: 1 + # Use the lower-level cache actions for the success cache, so that we can store the cache even on failed builds + - name: restore package-docs cache + uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4 + with: + path: .cache/package-docs + key: ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-next-${{ github.run_id }} + restore-keys: | + ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-next- + + - name: build API reference (beta) + run: yarn backstage-repo-tools package-docs + + - name: upload API reference (beta) + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: next-reference-beta + path: type-docs/ + + # Always save success cache even if there were failures, that way it can be used in re-triggered builds + - name: save package-docs cache + uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4 + if: always() + with: + path: .cache/package-docs + key: ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-next-${{ github.run_id }} + # Also build and upload storybook - name: storybook yarn install run: yarn install --immutable @@ -271,3 +323,15 @@ jobs: - name: build microsite run: yarn build working-directory: microsite + + - name: download stable reference (beta) + uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + with: + name: stable-reference-beta + path: api/stable/ + + - name: download next reference (beta) + uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + with: + name: next-reference-beta + path: api/next/ diff --git a/ADOPTERS.md b/ADOPTERS.md index e97c213adf..d20fd4a8b2 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -284,3 +284,4 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [Scalepoint](https://scalepoint.com) | [Yuriy Ostapenko](https://github.com/yuriyostapenko), [Dmytro Pasko](https://github.com/dimapasko), [Yuri Sedykh ](https://github.com/yuiri-sedykh), [Bartosz Wisniewski](https://github.com/Epgor) | Internal Developer Portal: Software Catalog with infrastructure resources and dependencies, Software Templates, TechDocs, Tech Radar, Kubernetes and Observability, as well as custom CI/CD components | | [VR](https://www.vr.com.br) | [Felipe Gomes](https://www.linkedin.com/in/felipe-gomes-da-silva), [Tiago Garcia](https://www.linkedin.com/in/tiago-garcia-6301a925) | Internal portal for development squads, service catalog, documentation hub, and application mapping for the entire company ecosystem, simplifying cloud infrastructure construction through templates. | | [Plata](https://platacard.mx/en) | [Shamil Ganiev](https://github.com/ganievs) | Internal Developer Portal for the whole engineering team | +| [Syngenta Digital](https://www.syngentadigital.com) | [Bitan Mallick](https://www.linkedin.com/in/bitanmallick) | Internal developer portal, designed to empower developers and streamline workflows. We use software catalog, tech-radar, software templates, tech-docs and various custom plugins to ensure an efficient and collaborative development experience. | diff --git a/LABELS.md b/LABELS.md index 7f8804f75a..32d5c7f095 100644 --- a/LABELS.md +++ b/LABELS.md @@ -26,6 +26,7 @@ These labels indicate what is needed to move an issue forward before it can be a - `needs:bep` - The issue is an advanced addition that needs a [Backstage Enhancement Proposal](./beps/README.md). - `needs:direction` - The issue needs direction from the owners of the area. +- `needs:discussion` - The way forward for this issue is unclear and it needs further discussion with the author and other participants. - `needs:more-info` - The issue needs more information from the author. - `needs:motivation` - It is not clear why this change is needed. The author should provide motivation for the change, for instance by giving examples of concrete use cases or scenarios. - `needs:repro` - The issue cannot be reproduced by the owners of the area. The author should provide more information to help them reproduce the issue, if possible with a minimal reproduction repository. @@ -81,11 +82,17 @@ These labels indicate that an issue is related to specific domains of expertise. - `domain:tooling` - Tooling and automation in Node.js and GitHub Actions. - `domain:web` - Frontend development using TypeScript and React. +## Workflow Labels + +These labels indicate a workflow status for the owners. + +- `workflow:do-not-merge` - The Pull Request should not be merged. +- `workflow:before-release` - Should be handled before the next main-line release. +- `workflow:after-release` - Should be handled after the next main-line release. +- `workflow:after-vacations` - To be handled once the owners return from vacation. + ## General Labels -- `after vacations` - To be handled once the owners return from vacation. -- `do not merge` - The pull request should not be merged. -- `fix before release` - Should be handled before the next release. - `good first issue` - Good for new contributors. - `stale` - The issue or pull request has not seen any activity for a while and will be closed if no further activity is seen. - `no stale` - The issue or pull request should not be closed due to inactivity. diff --git a/app-config.yaml b/app-config.yaml index 89e417fad3..eaee401d09 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -64,83 +64,11 @@ backend: # See README.md in the proxy-backend plugin for information on the configuration format proxy: endpoints: - '/circleci/api': - target: https://circleci.com/api/v1.1 - headers: - Circle-Token: ${CIRCLECI_AUTH_TOKEN} - - '/jenkins/api': - target: http://localhost:8080 - headers: - Authorization: ${JENKINS_BASIC_AUTH_HEADER} - - '/travisci/api': - target: https://api.travis-ci.com - changeOrigin: true - headers: - Authorization: ${TRAVISCI_AUTH_TOKEN} - travis-api-version: '3' - - '/newrelic/apm/api': - target: https://api.newrelic.com/v2 - headers: - X-Api-Key: ${NEW_RELIC_REST_API_KEY} - allowedHeaders: - - link - - '/newrelic/api': - target: https://api.newrelic.com - headers: - X-Api-Key: ${NEW_RELIC_USER_KEY} - '/pagerduty': target: https://api.pagerduty.com headers: Authorization: Token token=${PAGERDUTY_TOKEN} - '/buildkite/api': - target: https://api.buildkite.com/v2/ - headers: - Authorization: ${BUILDKITE_TOKEN} - - '/sentry/api': - target: https://sentry.io/api/ - allowedMethods: ['GET'] - headers: - Authorization: ${SENTRY_TOKEN} - - '/ilert': - target: https://api.ilert.com - allowedMethods: ['GET', 'POST', 'PUT'] - allowedHeaders: ['Authorization'] - headers: - Authorization: ${ILERT_AUTH_HEADER} - - '/airflow': - target: https://your.airflow.instance.com/api/v1 - headers: - Authorization: ${AIRFLOW_BASIC_AUTH_HEADER} - - '/gocd': - target: https://your.gocd.instance.com/go/api - allowedMethods: ['GET'] - allowedHeaders: ['Authorization'] - headers: - Authorization: Basic ${GOCD_AUTH_CREDENTIALS} - - '/dynatrace': - target: https://your.dynatrace.instance.com/api/v2 - headers: - Authorization: 'Api-Token ${DYNATRACE_ACCESS_TOKEN}' - - '/stackstorm': - target: https://your.stackstorm.instance.com/api - headers: - St2-Api-Key: ${ST2_API_KEY} - - '/puppetdb': - target: https://your.puppetdb.instance.com - organization: name: My Company @@ -157,46 +85,6 @@ techdocs: publisher: type: 'local' # Alternatives - 'googleGcs' or 'awsS3' or 'azureBlobStorage' or 'openStackSwift'. Read documentation for using alternatives. -dynatrace: - baseUrl: https://your.dynatrace.instance.com - -nomad: - addr: 0.0.0.0 - -# Score-cards sample configuration. -scorecards: - jsonDataUrl: https://raw.githubusercontent.com/Oriflame/backstage-plugins/main/plugins/score-card/sample-data/ - wikiLinkTemplate: https://link-to-wiki/{id} - -sentry: - organization: my-company - -rollbar: - organization: my-company - # NOTE: The rollbar-backend & accountToken key may be deprecated in the future (replaced by a proxy config) - accountToken: my-rollbar-account-token - -lighthouse: - baseUrl: http://localhost:3003 - -kubernetes: - serviceLocatorMethod: - type: 'multiTenant' - clusterLocatorMethods: - - type: 'config' - clusters: [] - -kafka: - clientId: backstage - clusters: - - name: cluster - dashboardUrl: https://akhq.io/ - brokers: - - localhost:9092 - -allure: - baseUrl: http://localhost:5050/allure-docker-service - integrations: github: - host: github.com @@ -269,26 +157,6 @@ catalog: plugins: - catalog - search - - processors: - ldapOrg: - ### Example for how to add your enterprise LDAP server - # providers: - # - target: ldaps://ds.example.net - # bind: - # dn: uid=ldap-reader-user,ou=people,ou=example,dc=example,dc=net - # secret: ${LDAP_SECRET} - # users: - # dn: ou=people,ou=example,dc=example,dc=net - # options: - # filter: (uid=*) - # map: - # description: l - # groups: - # dn: ou=access,ou=groups,ou=example,dc=example,dc=net - # options: - # filter: (&(objectClass=some-group-class)(!(groupType=email))) - locations: # Add a location here to ingest it, for example from a URL: # @@ -325,13 +193,14 @@ catalog: target: ../../plugins/scaffolder-backend/sample-templates/all-templates.yaml rules: - allow: [Template] + scaffolder: # Use to customize default commit author info used when new components are created - # defaultAuthor: - # name: Scaffolder - # email: scaffolder@backstage.io + defaultAuthor: + name: Scaffolder + email: scaffolder@backstage.io # Use to customize the default commit message when new components are created - # defaultCommitMessage: 'Initial commit' + defaultCommitMessage: 'Initial commit' auth: ### Add auth.keyStore.provider to more granularly control how to store JWK data when running @@ -422,80 +291,5 @@ auth: myproxy: {} guest: {} -costInsights: - engineerCost: 200000 - engineerThreshold: 0.5 - products: - computeEngine: - name: Compute Engine - icon: compute - cloudDataflow: - name: Cloud Dataflow - icon: data - cloudStorage: - name: Cloud Storage - icon: storage - bigQuery: - name: BigQuery - icon: search - events: - name: Events - icon: data - metrics: - DAU: - name: Daily Active Users - default: true - MSC: - name: Monthly Subscribers - currencies: - engineers: - label: 'Engineers 🛠' - unit: 'engineer' - usd: - label: 'US Dollars 💵' - kind: 'USD' - unit: 'dollar' - prefix: '$' - rate: 1 - carbonOffsetTons: - label: 'Carbon Offset Tons ♻️⚖️s' - kind: 'CARBON_OFFSET_TONS' - unit: 'carbon offset ton' - rate: 3.5 - beers: - label: 'Beers 🍺' - kind: 'BEERS' - unit: 'beer' - rate: 4.5 - pintsIceCream: - label: 'Pints of Ice Cream 🍦' - kind: 'PINTS_OF_ICE_CREAM' - unit: 'ice cream pint' - rate: 5.5 - -pagerDuty: - eventsBaseUrl: 'https://events.pagerduty.com/v2' - -jenkins: - instances: - - name: default - baseUrl: https://jenkins.example.com - username: backstage-bot - apiKey: 123456789abcdef0123456789abcedf012 - -azureDevOps: - host: dev.azure.com - token: my-token - organization: my-company - -apacheAirflow: - baseUrl: https://your.airflow.instance.com - -gocd: - baseUrl: https://your.gocd.instance.com - -stackstorm: - webUrl: https://your.stackstorm.webui.instance.com - permission: enabled: true diff --git a/canon-docs/src/app/(docs)/releases/page.mdx b/canon-docs/src/app/(docs)/releases/page.mdx index e71779e854..dcb96e5bf7 100644 --- a/canon-docs/src/app/(docs)/releases/page.mdx +++ b/canon-docs/src/app/(docs)/releases/page.mdx @@ -1,56 +1,77 @@ # Releases +## Version 0.4.0 + +### Main updates + +- ✨ Add new `Tab` component - [#29996](https://github.com/backstage/backstage/pull/29996) +- ✨ Add `truncate` prop to `Text` and `Heading` - [#29988](https://github.com/backstage/backstage/pull/29988) +- ✨ Add combobox option to `Menu` - [#29986](https://github.com/backstage/backstage/pull/29986) +- ✨ Add icon prop on `TextField` - [#29820](https://github.com/backstage/backstage/pull/29820) +- Improve icon props on `Button` and `IconButton` - [#29667](https://github.com/backstage/backstage/pull/29667) +- Improve the way we treat custom render on `Text` and `Heading` - [#29989](https://github.com/backstage/backstage/pull/29989) +- Improve `Menu` styles - [#29986](https://github.com/backstage/backstage/pull/29986) +- Improve `TextField` styles - [#29974](https://github.com/backstage/backstage/pull/29974) +- Improve clear button on `TextField` - [#29878](https://github.com/backstage/backstage/pull/29878) + +### Notable fixes + +- Fix spacing props on all layout components - [#30013](https://github.com/backstage/backstage/pull/30013) +- Fix - Pin Base UI version - [#29782](https://github.com/backstage/backstage/pull/29782) +- Fix - Clicking `Select` label moves focus to trigger - [#29755](https://github.com/backstage/backstage/pull/29755) +- Fix `DataTable.Pagination` count issue - [#29688](https://github.com/backstage/backstage/pull/29688) + ## Version 0.3.0 ### Main updates -- Add `DataTable` component - ([#29484](https://github.com/backstage/backstage/pull/29484), [#29603](https://github.com/backstage/backstage/pull/29603)) -- Add `Select` component - ([#29440](https://github.com/backstage/backstage/pull/29440)) -- Add `Avatar` component - ([#29594](https://github.com/backstage/backstage/pull/29594)) -- Add `Collapsible` component - ([#29617](https://github.com/backstage/backstage/pull/29617)) -- Add `TextField` component instead of `Field` + `Input` - ([#29364](https://github.com/backstage/backstage/pull/29364)) -- Add `TableCellProfile` - ([#29600](https://github.com/backstage/backstage/pull/29600)) -- Add breakpoint hooks - `up()` and `down()` - ([#29564](https://github.com/backstage/backstage/pull/29564)) -- Add gray scale css tokens - ([#29543](https://github.com/backstage/backstage/pull/29543)) -- Update CSS styling API using `[data-___]` instead of class names for props - ([#29560](https://github.com/backstage/backstage/pull/29560)) -- Update `Checkbox` dark mode - ([#29544](https://github.com/backstage/backstage/pull/29544)) -- Update `Container` styles - ([#29475](https://github.com/backstage/backstage/pull/29475)) -- Update `Menu` styles - ([#29351](https://github.com/backstage/backstage/pull/29351)) -- Fix `Select` styles on small sizes + with long option names - ([#29545](https://github.com/backstage/backstage/pull/29545)) -- Fix render prop on `Link` - ([#29247](https://github.com/backstage/backstage/pull/29247)) -- Remove `Field` from `TextField` + `Select` - ([#29482](https://github.com/backstage/backstage/pull/29482)) -- Update `textDecoration` to `none` on `Text` / `Heading` - ([#29357](https://github.com/backstage/backstage/pull/29357)) +- Add `DataTable` component - [#29484](https://github.com/backstage/backstage/pull/29484), [#29603](https://github.com/backstage/backstage/pull/29603) +- Add `Select` component - [#29440](https://github.com/backstage/backstage/pull/29440) +- Add `Avatar` component - [#29594](https://github.com/backstage/backstage/pull/29594) +- Add `Collapsible` component - [#29617](https://github.com/backstage/backstage/pull/29617) +- Add `TextField` component instead of `Field` + `Input` - [#29364](https://github.com/backstage/backstage/pull/29364) +- Add `TableCellProfile` - [#29600](https://github.com/backstage/backstage/pull/29600) +- Add breakpoint hooks - `up()` and `down()` - [#29564](https://github.com/backstage/backstage/pull/29564) +- Add gray scale css tokens - [#29543](https://github.com/backstage/backstage/pull/29543) +- Update CSS styling API using `[data-___]` instead of class names for props - [#29560](https://github.com/backstage/backstage/pull/29560) +- Update `Checkbox` dark mode - [#29544](https://github.com/backstage/backstage/pull/29544) +- Update `Container` styles - [#29475](https://github.com/backstage/backstage/pull/29475) +- Update `Menu` styles - [#29351](https://github.com/backstage/backstage/pull/29351) +- Fix `Select` styles on small sizes + with long option names - [#29545](https://github.com/backstage/backstage/pull/29545) +- Fix render prop on `Link` - [#29247](https://github.com/backstage/backstage/pull/29247) +- Remove `Field` from `TextField` + `Select` - [#29482](https://github.com/backstage/backstage/pull/29482) +- Update `textDecoration` to `none` on `Text` / `Heading` - [#29357](https://github.com/backstage/backstage/pull/29357) ### Notable fixes -- Docs - Use stories from Storybook for all examples in Nextjs - ([#29306](https://github.com/backstage/backstage/pull/29306)) -- Docs - Add release page (this one 🤗) - ([#29461](https://github.com/backstage/backstage/pull/29461)) -- Docs - Add docs for Menu, Link - ([#29576](https://github.com/backstage/backstage/pull/29576)) -- Fix CSS watch mode - ([#29352](https://github.com/backstage/backstage/pull/29352)) +- Docs - Use stories from Storybook for all examples in Nextjs - [#29306](https://github.com/backstage/backstage/pull/29306) +- Docs - Add release page (this one 🤗) - [#29461](https://github.com/backstage/backstage/pull/29461) +- Docs - Add docs for Menu, Link - [#29576](https://github.com/backstage/backstage/pull/29576) +- Fix CSS watch mode - [#29352](https://github.com/backstage/backstage/pull/29352) ## Version 0.2.0 ### Main updates -- New `Tooltip` component ([#29241](https://github.com/backstage/backstage/pull/29241)) -- New `Menu` component. ([#29151](https://github.com/backstage/backstage/pull/29151)) -- New `IconButton` component. ([#29239](https://github.com/backstage/backstage/pull/29239)) -- New `ScrollArea` component. ([#29240](https://github.com/backstage/backstage/pull/29240)) -- Improve `Button` & `Checkbox` styles. ([#29127](https://github.com/backstage/backstage/pull/29127)) ([#28789](https://github.com/backstage/backstage/pull/28789)) -- Improve `Text` styles. ([#29200](https://github.com/backstage/backstage/pull/29200)) -- Renamed `CanonProvider` to `IconProvider`. ([#29002](https://github.com/backstage/backstage/pull/29002)) -- Added about 40+ new icons. ([#29264](https://github.com/backstage/backstage/pull/29264)) -- Simplified styling into a unique styles.css file ([#29199](https://github.com/backstage/backstage/pull/29199)) -- Added Canon styles to Backstage ([#29137](https://github.com/backstage/backstage/pull/29137)) -- Update global CSS tokens ([#28804](https://github.com/backstage/backstage/pull/28804)) -- Merge Stack + Inline into Flex ([#28634](https://github.com/backstage/backstage/pull/28634)) +- New `Tooltip` component - [#29241](https://github.com/backstage/backstage/pull/29241) +- New `Menu` component - [#29151](https://github.com/backstage/backstage/pull/29151) +- New `IconButton` component - [#29239](https://github.com/backstage/backstage/pull/29239) +- New `ScrollArea` component - [#29240](https://github.com/backstage/backstage/pull/29240) +- Improve `Button` & `Checkbox` styles - [#29127](https://github.com/backstage/backstage/pull/29127), [#28789](https://github.com/backstage/backstage/pull/28789) +- Improve `Text` styles - [#29200](https://github.com/backstage/backstage/pull/29200) +- Renamed `CanonProvider` to `IconProvider` - [#29002](https://github.com/backstage/backstage/pull/29002) +- Added about 40+ new icons - [#29264](https://github.com/backstage/backstage/pull/29264) +- Simplified styling into a unique styles.css file - [#29199](https://github.com/backstage/backstage/pull/29199) +- Added Canon styles to Backstage - [#29137](https://github.com/backstage/backstage/pull/29137) +- Update global CSS tokens - [#28804](https://github.com/backstage/backstage/pull/28804) +- Merge Stack + Inline into Flex - [#28634](https://github.com/backstage/backstage/pull/28634) ### Notable fixes: -- Improve `Button` types. ([#29205](https://github.com/backstage/backstage/pull/29205)) -- Move font weight and family back to each components ([#28972](https://github.com/backstage/backstage/pull/28972)) -- Fix custom values in spacing props ([#28770](https://github.com/backstage/backstage/pull/28770)) -- Multiple updates on the Canon Docs site ([#28760](https://github.com/backstage/backstage/pull/28760)) ([#28591](https://github.com/backstage/backstage/pull/28591)) +- Improve `Button` types - [#29205](https://github.com/backstage/backstage/pull/29205) +- Move font weight and family back to each components - [#28972](https://github.com/backstage/backstage/pull/28972) +- Fix custom values in spacing props - [#28770](https://github.com/backstage/backstage/pull/28770) +- Multiple updates on the Canon Docs site - [#28760](https://github.com/backstage/backstage/pull/28760), [#28591](https://github.com/backstage/backstage/pull/28591) ## Version 0.1.0 diff --git a/canon-docs/src/app/(playground)/playground/page.tsx b/canon-docs/src/app/(playground)/playground/page.tsx index 3f1675551e..671d644993 100644 --- a/canon-docs/src/app/(playground)/playground/page.tsx +++ b/canon-docs/src/app/(playground)/playground/page.tsx @@ -80,11 +80,11 @@ const Content = () => { const Line = ({ content, title }: { content: ReactNode; title: string }) => { return ( - + {title} {content} - + ); }; diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md index a22a7d94d9..aeba3fddbb 100644 --- a/docs/features/kubernetes/configuration.md +++ b/docs/features/kubernetes/configuration.md @@ -203,6 +203,28 @@ or the [`AwsEKSClusterProcessor`](https://backstage.io/docs/reference/plugin-catalog-backend-module-aws.awseksclusterprocessor/) to automatically update the set of clusters tracked by Backstage. +For this method to work any entity that would be using this `Resource` to help drive the Kubernetes details in the Catalog's Entity pages needs to have a `dependsOn` relationship setup. Here's a quick example: + +```yaml +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + annotations: + backstage.io/kubernetes-id: dice-roller + backstage.io/kubernetes-namespace: default + name: dice-roller + description: It rolls dice + tags: + - go +spec: + type: service + lifecycle: production + owner: guest + dependsOn: ['resource:my-cluster'] +``` + +This example assumes it's using the default namespace, if that's not the case for you then make sure to include it like this: `resource:my-namespace/my-cluster`. + #### `config` This cluster locator method will read cluster information from your app-config diff --git a/docs/features/search/declarative-integration.md b/docs/features/search/declarative-integration.md index c331d810ef..b8f1c6ce5c 100644 --- a/docs/features/search/declarative-integration.md +++ b/docs/features/search/declarative-integration.md @@ -182,7 +182,7 @@ export default createPlugin({ }) ``` -Here is the `plugins/techdocs/alpha.tsx` final version, and you can also take a look at the [actual implementation](https://github.com/backstage/backstage/blob/master/plugins/techdocs/src/alpha.tsx) of a custom `TechDocs` search result item: +Here is the `plugins/techdocs/alpha/index.tsx` final version, and you can also take a look at the [actual implementation](https://github.com/backstage/backstage/blob/master/plugins/techdocs/src/alpha/index.tsx) of a custom `TechDocs` search result item: ```tsx // plugins/techdocs/alpha.tsx diff --git a/docs/features/software-catalog/well-known-annotations.md b/docs/features/software-catalog/well-known-annotations.md index 974fa80993..42cefd3adb 100644 --- a/docs/features/software-catalog/well-known-annotations.md +++ b/docs/features/software-catalog/well-known-annotations.md @@ -115,6 +115,20 @@ the TechDocs in the TechDocs page or needing multiple builds of the same docs. This is for situations where you have complex systems where they share a single repo, and likely a single TechDoc location. +### backstage.io/techdocs-entity-path + +```yaml +# Example: +metadata: + annotations: + backstage.io/techdocs-entity: component:default/example + backstage.io/techdocs-entity-path: /path/to/this/component +``` + +The value of this annotation informs of the path to this component's TechDocs within an external entity that owns the TechDocs. +In conjunction with [backstage.io/techdocs-entity](#backstageiotechdocs-entity) this allows for deep linking into the TechDocs of +another entity, not just linking to the root of another entities TechDocs. + ### backstage.io/view-url, backstage.io/edit-url ```yaml diff --git a/docs/features/techdocs/how-to-guides.md b/docs/features/techdocs/how-to-guides.md index 8a95c86533..7d8c99ffda 100644 --- a/docs/features/techdocs/how-to-guides.md +++ b/docs/features/techdocs/how-to-guides.md @@ -942,6 +942,35 @@ metadata: backstage.io/techdocs-entity: system:default/example ``` +### Deep linking into TechDocs + +The `backstage.io/techdocs-entity-path` annotation can be use to deep link into a specific page within the components TechDocs. +This can be used in conjunction with `backstage.io/techdocs-entity` or standalone. + +```yaml +apiVersion: backstage.io/v1alpha1 +kind: System +metadata: + name: example + namespace: default + title: Example + description: This is the parent entity + annotations: + backstage.io/techdocs-ref: dir:. + +--- +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: example-platfrom + title: Example Application Platform + namespace: default + description: This is the child entity + annotations: + backstage.io/techdocs-entity: system:default/example + backstage.io/techdocs-entity-path: /path/to/component/docs +``` + ## How to resolve broken links from moved or renamed pages in your documentation site TechDocs supports using the [mkdocs-redirects](https://github.com/mkdocs/mkdocs-redirects/tree/master) plugin to create a redirect map for any TechDocs site. This allows broken links from renamed or moved pages in your site to be redirected to their specified replacement. diff --git a/docs/frontend-system/architecture/10-app.md b/docs/frontend-system/architecture/10-app.md index 0fa4ba04cc..f0d7ff3d1e 100644 --- a/docs/frontend-system/architecture/10-app.md +++ b/docs/frontend-system/architecture/10-app.md @@ -77,3 +77,98 @@ app: ``` Note that you do not need to manually exclude packages that you also import explicitly in code, since plugin instances are deduplicated by the app. You will never end up with duplicate plugin installations except if they are in fact two different plugin instances with different IDs. + +## Plugin Info Resolution + +When a plugin is installed in an app it may provide sources of information about the plugin that can be useful to end users and admins. This includes things like what version of a plugin is running, what team owns the plugin, and who to contact for support. You can read more about how the plugins provide this information in the [plugins `info` option section](./15-plugins.md#info). + +By default the app will pick a few common fields from `package.json` files, and assume that the opaque manifests are `catalog-info.yaml` files that some information can be gathered from too. This information will then be available via the `info()` method on plugin instances, returning a structure of the `FrontendPluginInfo` type. + +### Extending Plugin Info + +The default plugin info is intended as a base to build upon. As part of setting up an app you can both customize the way that the plugin info is resolved, as well as extend the `FrontendPluginInfo` type to include more information. + +In order to extend the `FrontendPluginInfo` type you use [TypeScript module augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation). This makes it possible to extend the `FrontendPluginInfo` interface with additional fields, which you can then add custom resolution logic for as well as access within the app. For example, you might add a `slackChannel` field as follows: + +```ts +declare module '@backstage/frontend-plugin-api' { + interface FrontendPluginInfo { + /** + * The slack channel to use for support requests for this plugin. + */ + slackChannel?: string; + } +} +``` + +### Customizing Plugin Info Resolution + +With the new `slackChannel` field in place, we now need to provide a custom resolver that knows how to extract this information from the plugin information sources. This is done by passing a custom `pluginInfoResolver` to `createApp`, which in our example is declared like this: + +```ts title="pluginInfoResolver.ts" +import { createPluginInfoResolver } from '@backstage/frontend-plugin-api'; + +// It is recommended to keep the above module augmentation in this file too + +export const pluginInfoResolver: FrontendPluginInfoResolver = async ctx => { + // In our particular example app we assume that all plugin manifests are catalog-info.yaml files + const manifest = (await ctx.manifest?.()) as Entity | undefined; + + // Call the default resolver to populate common fields + const { info } = await ctx.defaultResolver({ + packageJson: await ctx.packageJson(), + manifest: manifest, + }); + + // In this example the catalog model has been extended with a metadata.slackChannel field + const slackChannel = manifest?.metadata?.slackChannel?.toString(); + + if (slackChannel) { + info.slackChannel = slackChannel; + info.links = [ + ...(info.links ?? []), + { + title: 'Slack Channel', + url: `https://our-workspace.enterprise.slack.com/archives/${slackChannel}`, + }, + ]; + } + + return { info }; +}; +``` + +And included in the app as follows: + +```ts title="App.tsx" +import { pluginInfoResolver } from './pluginInfoResolver'; + +const app = createApp({ + pluginInfoResolver, + // ... other options +}); +``` + +### Overriding Plugin Info + +Another way to customize the plugin info is to use the `app.pluginOverrides` static configuration key. These overrides are applied after the plugin info has been resolved as a final step before making it available to users. These overrides are particularly useful to override information in third-party plugins. For example, if your organization has an individual team that is responsible for the maintenance of the Software Catalog, you might configure the following override: + +```yaml +app: + pluginOverrides: + - match: + pluginId: catalog + info: + ownerEntityRefs: [catalog-owners] +``` + +You can match on both the `pluginId` and/or `packageName` of the plugin, although the `packageName` will only be supported if the plugin provides an loader for the `package.json` file. Using `//` you are also able to use a regex pattern for this matching. For example, if you wanted to override the owner for all plugins from the `@acme` namespace, you could do the following: + +```yaml +app: + pluginOverrides: + - match: + packageName: /@acme/.*/ + info: + ownerEntityRefs: [acme-owners] +``` diff --git a/docs/frontend-system/architecture/15-plugins.md b/docs/frontend-system/architecture/15-plugins.md index 825f2a3982..6d2b14d77f 100644 --- a/docs/frontend-system/architecture/15-plugins.md +++ b/docs/frontend-system/architecture/15-plugins.md @@ -53,6 +53,35 @@ These are the routes that the plugin exposes to the app. The `routes` option dec This is a list of feature flag declarations that your plugin provides to the app. This makes sure that the feature flags are correctly registered and can be toggled in the app. To read a feature flag you can use the feature flags [Utility API](../architecture/33-utility-apis.md), accessible via `featureFlagsApiRef`. +### `info` option + +This options is used to provide loaders for different sources of information about the plugin that may be useful to users and admins. The two available loaders are `packageJson` and `manifest`, and a plugin can use either or both as needed. The resulting information is available via the `info()` method on the plugin instance once it is installed in an app, but it is up to each app to decide how to derive the information from the provided sources. + +The `info.packageJson` loader **MUST** be used by all plugins that are implemented within their own package, and it should load the `package.json` file for the plugin package. Typical usage looks like this: + +```ts +export default createFrontendPlugin({ + pluginId: 'my-plugin', + info: { + packageJson: () => import('../package.json'), + }, + extensions: [...], +}); +``` + +The `info.manifest` loader is used to point to an opaque plugin manifest. This **MUST ONLY** be used by plugins that are intended for use within a single organization. Plugins that are published to an open package registry should **NOT** use this loader. The loader is useful for adding additional internal metadata associated with the plugin, and it is up to the Backstage app to decide how these manifests are parsed and used. The default manifest parser in an app created with `createApp` from `@backstage/frontend-defaults` is able to parse the default `catalog-info.yaml` format and built-in fields such as `metadata.links` and `spec.owner`. + +Typical usage looks like this: + +```ts +export default createFrontendPlugin({ + pluginId: '...', + info: { + manifest: () => import('../catalog-info.yaml'), + }, +}); +``` + ## Installing a Plugin in an App A plugin instance is considered a frontend feature and can be installed directly in any Backstage frontend app. See the [app documentation](./10-app.md) for more information about the different ways in which you can install new features in an app. diff --git a/docs/notifications/notificationSettings.png b/docs/notifications/notificationSettings.png index cdad63d764..e135a3f194 100644 Binary files a/docs/notifications/notificationSettings.png and b/docs/notifications/notificationSettings.png differ diff --git a/docs/releases/v1.40.0-next.0-changelog.md b/docs/releases/v1.40.0-next.0-changelog.md new file mode 100644 index 0000000000..10ed249086 --- /dev/null +++ b/docs/releases/v1.40.0-next.0-changelog.md @@ -0,0 +1,1490 @@ +# Release v1.40.0-next.0 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.40.0-next.0](https://backstage.github.io/upgrade-helper/?to=1.40.0-next.0) + +## @backstage/backend-plugin-api@1.4.0-next.0 + +### Minor Changes + +- 664c07a: Added `coreServices.actionsRegistry` and `coreServices.actions` to allow registration of distributed actions from plugins, and the ability to invoke these actions + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-permission-node@0.10.1-next.0 + +## @backstage/backend-test-utils@1.6.0-next.0 + +### Minor Changes + +- c999c25: Added mock implementations for `ActionsService` and `ActionsRegistryService` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/backend-defaults@0.10.1-next.0 + - @backstage/backend-app-api@1.2.4-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + +## @backstage/canon@0.5.0-next.0 + +### Minor Changes + +- 24b45ef: Fixes spacing props on layout components and aligned on naming for the Grid component. You should now call the Grid root component using <Grid.Root /> instead of just . + +### Patch Changes + +- 269316d: Remove leftover console.log from Container component. + +## @backstage/create-app@0.7.0-next.0 + +### Minor Changes + +- 30474c4: Add .cache directory to shipped gitignore. + +## @backstage/repo-tools@0.14.0-next.0 + +### Minor Changes + +- bf9a173: Add support for caching the per-package output from the `package-docs` command. + +### Patch Changes + +- 2d20024: Fix an issue where errors were not printed to console when running `backstage-repo-tools schema openapi generate` without the `--watch` flag. +- e643ee4: Add missing highlight language for the `package-docs` command. +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/cli-node@0.2.13 + - @backstage/config-loader@1.10.1 + +## @backstage/plugin-catalog@1.31.0-next.0 + +### Minor Changes + +- ec7b35d: Introduced `backstage.io/techdocs-entity-path` annotation which allows deep linking into another entities TechDocs in conjunction with `backstage.io/techdocs-entity`. + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + - @backstage/plugin-techdocs-common@0.1.1-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + - @backstage/plugin-search-react@1.9.1-next.0 + - @backstage/integration-react@1.2.7 + +## @backstage/plugin-techdocs@1.13.0-next.0 + +### Minor Changes + +- ec7b35d: Introduced `backstage.io/techdocs-entity-path` annotation which allows deep linking into another entities TechDocs in conjunction with `backstage.io/techdocs-entity`. + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + - @backstage/plugin-techdocs-common@0.1.1-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + - @backstage/plugin-search-react@1.9.1-next.0 + - @backstage/integration-react@1.2.7 + +## @backstage/plugin-techdocs-react@1.3.0-next.0 + +### Minor Changes + +- ec7b35d: Introduced `backstage.io/techdocs-entity-path` annotation which allows deep linking into another entities TechDocs in conjunction with `backstage.io/techdocs-entity`. + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/plugin-techdocs-common@0.1.1-next.0 + +## @backstage/backend-app-api@1.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + +## @backstage/backend-defaults@0.10.1-next.0 + +### Patch Changes + +- c999c25: Added some default implementations for the `ActionsService` and `ActionsRegistryService` that allow registration of actions for a particular plugin. +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/backend-app-api@1.2.4-next.0 + - @backstage/cli-node@0.2.13 + - @backstage/config-loader@1.10.1 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + - @backstage/plugin-permission-node@0.10.1-next.0 + +## @backstage/backend-dynamic-feature-service@0.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/plugin-catalog-backend@2.0.1-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/backend-defaults@0.10.1-next.0 + - @backstage/plugin-events-backend@0.5.3-next.0 + - @backstage/cli-node@0.2.13 + - @backstage/config-loader@1.10.1 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + - @backstage/plugin-permission-node@0.10.1-next.0 + - @backstage/plugin-search-backend-node@1.3.12-next.0 + - @backstage/backend-openapi-utils@0.5.4-next.0 + - @backstage/plugin-app-node@0.1.34-next.0 + +## @backstage/backend-openapi-utils@0.5.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + +## @backstage/core-compat-api@0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + +## @backstage/dev-utils@1.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.18.1-next.0 + - @backstage/integration-react@1.2.7 + +## @backstage/frontend-app-api@0.11.3-next.0 + +### Patch Changes + +- c38c9e8: Implemented support for the `plugin.info()` method in specialized apps with a default resolved for `package.json` and `catalog-info.yaml`. The default resolution logic can be overridden via the `pluginInfoResolver` option to `createSpecializedApp`, and plugin-specific overrides can be applied via the new `app.pluginOverrides` key in static configuration. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/frontend-defaults@0.2.3-next.0 + +## @backstage/frontend-defaults@0.2.3-next.0 + +### Patch Changes + +- fa5650c: Forwarded the new `pluginInfoResolver` option for `createApp`. +- Updated dependencies + - @backstage/plugin-app@0.1.10-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/frontend-app-api@0.11.3-next.0 + +## @backstage/frontend-dynamic-feature-loader@0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + +## @backstage/frontend-plugin-api@0.10.3-next.0 + +### Patch Changes + +- 9e3868f: Added a new optional `info` option to `createFrontendPlugin` that lets you provide a loaders for different sources of metadata information about the plugin. + + There are two available loaders. The first one is `info.packageJson`, which can be used to point to a `package.json` file for the plugin. This is recommended for any plugin that is defined within its own package, especially all plugins that are published to a package registry. Typical usage looks like this: + + ```ts + export default createFrontendPlugin({ + pluginId: '...', + info: { + packageJson: () => import('../package.json'), + }, + }); + ``` + + The second loader is `info.manifest`, which can be used to point to an opaque plugin manifest. This **MUST ONLY** be used by plugins that are intended for use within a single organization. Plugins that are published to an open package registry should **NOT** use this loader. The loader is useful for adding additional internal metadata associated with the plugin, and it is up to the Backstage app to decide how these manifests are parsed and used. The default manifest parser in an app created with `createApp` from `@backstage/frontend-defaults` is able to parse the default `catalog-info.yaml` format and built-in fields such as `spec.owner`. + + Typical usage looks like this: + + ```ts + export default createFrontendPlugin({ + pluginId: '...', + info: { + manifest: () => import('../catalog-info.yaml'), + }, + }); + ``` + +- 6f48f71: Added a new `useAppNode` hook, which can be used to get a reference to the `AppNode` from by the closest `ExtensionBoundary`. + +## @backstage/frontend-test-utils@0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.1.10-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/frontend-app-api@0.11.3-next.0 + +## @techdocs/cli@1.9.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.1-next.0 + - @backstage/plugin-techdocs-node@1.13.4-next.0 + +## @backstage/plugin-api-docs@0.12.8-next.0 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/plugin-catalog@1.31.0-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + +## @backstage/plugin-app@0.1.10-next.0 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/integration-react@1.2.7 + +## @backstage/plugin-app-backend@0.5.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/config-loader@1.10.1 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-app-node@0.1.34-next.0 + +## @backstage/plugin-app-node@0.1.34-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/config-loader@1.10.1 + +## @backstage/plugin-app-visualizer@0.1.20-next.0 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + +## @backstage/plugin-auth-backend@0.25.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## @backstage/plugin-auth-backend-module-auth0-provider@0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-backend@0.25.1-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## @backstage/plugin-auth-backend-module-github-provider@0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## @backstage/plugin-auth-backend-module-google-provider@0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## @backstage/plugin-auth-backend-module-guest-provider@0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-backend@0.25.1-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## @backstage/plugin-auth-backend-module-okta-provider@0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## @backstage/plugin-auth-node@0.6.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + +## @backstage/plugin-catalog-backend@2.0.1-next.0 + +### Patch Changes + +- 4654a78: Update `refresh_state_references.id` to be a big int +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + - @backstage/plugin-permission-node@0.10.1-next.0 + - @backstage/backend-openapi-utils@0.5.4-next.0 + +## @backstage/plugin-catalog-backend-module-aws@0.4.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/backend-defaults@0.10.1-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + +## @backstage/plugin-catalog-backend-module-azure@0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/backend-openapi-utils@0.5.4-next.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.4.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + +## @backstage/plugin-catalog-backend-module-gitea@0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + +## @backstage/plugin-catalog-backend-module-github@0.9.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@2.0.1-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-backend-module-github@0.9.1-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + +## @backstage/plugin-catalog-backend-module-gitlab@0.6.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/backend-defaults@0.10.1-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-backend-module-gitlab@0.6.7-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@2.0.1-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/backend-defaults@0.10.1-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + +## @backstage/plugin-catalog-backend-module-ldap@0.11.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + +## @backstage/plugin-catalog-backend-module-logs@0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@2.0.1-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + +## @backstage/plugin-catalog-backend-module-msgraph@0.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.6.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + +## @backstage/plugin-catalog-graph@0.4.20-next.0 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + +## @backstage/plugin-catalog-import@0.13.1-next.0 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + - @backstage/integration-react@1.2.7 + +## @backstage/plugin-catalog-node@1.17.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-permission-node@0.10.1-next.0 + +## @backstage/plugin-catalog-react@1.18.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + - @backstage/frontend-test-utils@0.3.3-next.0 + - @backstage/integration-react@1.2.7 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.18-next.0 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + +## @backstage/plugin-devtools@0.1.28-next.0 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + +## @backstage/plugin-devtools-backend@0.5.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/backend-defaults@0.10.1-next.0 + - @backstage/config-loader@1.10.1 + - @backstage/plugin-permission-node@0.10.1-next.0 + +## @backstage/plugin-events-backend@0.5.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + - @backstage/backend-openapi-utils@0.5.4-next.0 + +## @backstage/plugin-events-backend-module-aws-sqs@0.4.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + +## @backstage/plugin-events-backend-module-azure@0.2.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + +## @backstage/plugin-events-backend-module-bitbucket-server@0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + +## @backstage/plugin-events-backend-module-gerrit@0.2.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + +## @backstage/plugin-events-backend-module-github@0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + +## @backstage/plugin-events-backend-module-gitlab@0.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + +## @backstage/plugin-events-backend-module-google-pubsub@0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + +## @backstage/plugin-events-backend-test-utils@0.1.45-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.12-next.0 + +## @backstage/plugin-events-node@0.4.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + +## @backstage/plugin-gateway-backend@1.0.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + +## @backstage/plugin-home@0.8.9-next.0 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + +## @backstage/plugin-kubernetes@0.12.8-next.0 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + +## @backstage/plugin-kubernetes-backend@0.19.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-kubernetes-node@0.3.1-next.0 + - @backstage/plugin-permission-node@0.10.1-next.0 + +## @backstage/plugin-kubernetes-cluster@0.0.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.18.1-next.0 + +## @backstage/plugin-kubernetes-node@0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + +## @backstage/plugin-notifications@0.5.6-next.0 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + +## @backstage/plugin-notifications-backend@0.5.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + - @backstage/plugin-notifications-node@0.2.16-next.0 + - @backstage/plugin-signals-node@0.1.21-next.0 + +## @backstage/plugin-notifications-backend-module-email@0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-notifications-node@0.2.16-next.0 + +## @backstage/plugin-notifications-backend-module-slack@0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-notifications-node@0.2.16-next.0 + +## @backstage/plugin-notifications-node@0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-signals-node@0.1.21-next.0 + +## @backstage/plugin-org@0.6.40-next.0 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + +## @backstage/plugin-org-react@0.1.39-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.18.1-next.0 + +## @backstage/plugin-permission-backend@0.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-permission-node@0.10.1-next.0 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-permission-node@0.10.1-next.0 + +## @backstage/plugin-permission-node@0.10.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + +## @backstage/plugin-proxy-backend@0.6.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-proxy-node@0.1.5-next.0 + +## @backstage/plugin-proxy-node@0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + +## @backstage/plugin-scaffolder@1.31.1-next.0 + +### Patch Changes + +- d781b33: render details for composite property schemas +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + - @backstage/plugin-scaffolder-react@1.16.1-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + - @backstage/integration-react@1.2.7 + +## @backstage/plugin-scaffolder-backend@1.33.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.10-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.10-next.0 + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.2-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/backend-defaults@0.10.1-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.11-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.10-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.10-next.0 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.10-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.7.2-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.9-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + - @backstage/plugin-permission-node@0.10.1-next.0 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.10-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.10-next.0 + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.10-next.0 + +### Patch Changes + +- 9c8ff0c: Update pull request creation filter to include .gitignore files in the created pull request +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.10-next.0 + +### Patch Changes + +- 9c8ff0c: Update pull request creation filter to include .gitignore files in the created pull request +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/backend-defaults@0.10.1-next.0 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + +## @backstage/plugin-scaffolder-backend-module-github@0.7.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.9.2-next.0 + +### Patch Changes + +- 3d6493a: Support merge request labels in publish:gitlab:merge-request +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-notifications-node@0.2.16-next.0 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-scaffolder-node-test-utils@0.2.3-next.0 + +## @backstage/plugin-scaffolder-node@0.8.3-next.0 + +### Patch Changes + +- 9c8ff0c: Update pull request creation filter to include .gitignore files in the created pull request +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + +## @backstage/plugin-scaffolder-node-test-utils@0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-test-utils@1.6.0-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + +## @backstage/plugin-scaffolder-react@1.16.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + +## @backstage/plugin-search@1.4.27-next.0 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + - @backstage/plugin-search-react@1.9.1-next.0 + +## @backstage/plugin-search-backend@2.0.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/backend-defaults@0.10.1-next.0 + - @backstage/plugin-permission-node@0.10.1-next.0 + - @backstage/plugin-search-backend-node@1.3.12-next.0 + - @backstage/backend-openapi-utils@0.5.4-next.0 + +## @backstage/plugin-search-backend-module-catalog@0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-search-backend-node@1.3.12-next.0 + +## @backstage/plugin-search-backend-module-elasticsearch@1.7.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-search-backend-node@1.3.12-next.0 + +## @backstage/plugin-search-backend-module-explore@0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-search-backend-node@1.3.12-next.0 + +## @backstage/plugin-search-backend-module-pg@0.5.45-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-search-backend-node@1.3.12-next.0 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-search-backend-node@1.3.12-next.0 + +## @backstage/plugin-search-backend-module-techdocs@0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-techdocs-node@1.13.4-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-search-backend-node@1.3.12-next.0 + +## @backstage/plugin-search-backend-node@1.3.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + +## @backstage/plugin-search-react@1.9.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + +## @backstage/plugin-signals@0.0.20-next.0 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + +## @backstage/plugin-signals-backend@0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + - @backstage/plugin-signals-node@0.1.21-next.0 + +## @backstage/plugin-signals-node@0.1.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.49-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.13.0-next.0 + - @backstage/plugin-catalog@1.31.0-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + - @backstage/plugin-search-react@1.9.1-next.0 + - @backstage/integration-react@1.2.7 + +## @backstage/plugin-techdocs-backend@2.0.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-common@0.1.1-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/backend-defaults@0.10.1-next.0 + - @backstage/plugin-techdocs-node@1.13.4-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.4.3-next.0 + +## @backstage/plugin-techdocs-common@0.1.1-next.0 + +### Patch Changes + +- ec7b35d: Introduced `backstage.io/techdocs-entity-path` annotation which allows deep linking into another entities TechDocs in conjunction with `backstage.io/techdocs-entity`. + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + - @backstage/integration-react@1.2.7 + +## @backstage/plugin-techdocs-node@1.13.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-common@0.1.1-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + +## @backstage/plugin-user-settings@0.8.23-next.0 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + +## @backstage/plugin-user-settings-backend@0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/backend-defaults@0.10.1-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-signals-node@0.1.21-next.0 + +## example-app@0.2.110-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.31.1-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.18-next.0 + - @backstage/plugin-catalog-import@0.13.1-next.0 + - @backstage/plugin-catalog-graph@0.4.20-next.0 + - @backstage/plugin-notifications@0.5.6-next.0 + - @backstage/plugin-user-settings@0.8.23-next.0 + - @backstage/plugin-kubernetes@0.12.8-next.0 + - @backstage/plugin-api-docs@0.12.8-next.0 + - @backstage/plugin-devtools@0.1.28-next.0 + - @backstage/plugin-techdocs@1.13.0-next.0 + - @backstage/plugin-catalog@1.31.0-next.0 + - @backstage/plugin-signals@0.0.20-next.0 + - @backstage/plugin-search@1.4.27-next.0 + - @backstage/plugin-home@0.8.9-next.0 + - @backstage/plugin-org@0.6.40-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + - @backstage/frontend-app-api@0.11.3-next.0 + - @backstage/canon@0.5.0-next.0 + - @backstage/plugin-scaffolder-react@1.16.1-next.0 + - @backstage/cli@0.32.1 + - @backstage/plugin-catalog-react@1.18.1-next.0 + - @backstage/plugin-search-react@1.9.1-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.25-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.26-next.0 + - @backstage/integration-react@1.2.7 + +## example-app-next@0.0.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.31.1-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.18-next.0 + - @backstage/plugin-app-visualizer@0.1.20-next.0 + - @backstage/plugin-catalog-import@0.13.1-next.0 + - @backstage/plugin-catalog-graph@0.4.20-next.0 + - @backstage/plugin-notifications@0.5.6-next.0 + - @backstage/plugin-user-settings@0.8.23-next.0 + - @backstage/plugin-kubernetes@0.12.8-next.0 + - @backstage/plugin-api-docs@0.12.8-next.0 + - @backstage/plugin-techdocs@1.13.0-next.0 + - @backstage/plugin-catalog@1.31.0-next.0 + - @backstage/plugin-signals@0.0.20-next.0 + - @backstage/plugin-search@1.4.27-next.0 + - @backstage/plugin-home@0.8.9-next.0 + - @backstage/plugin-app@0.1.10-next.0 + - @backstage/plugin-org@0.6.40-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + - @backstage/frontend-defaults@0.2.3-next.0 + - @backstage/frontend-app-api@0.11.3-next.0 + - @backstage/canon@0.5.0-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + - @backstage/plugin-scaffolder-react@1.16.1-next.0 + - @backstage/cli@0.32.1 + - @backstage/plugin-catalog-react@1.18.1-next.0 + - @backstage/plugin-search-react@1.9.1-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.25-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.26-next.0 + - @backstage/integration-react@1.2.7 + +## app-next-example-plugin@0.0.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + +## example-backend@0.0.39-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@2.0.1-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/backend-defaults@0.10.1-next.0 + - @backstage/plugin-events-backend@0.5.3-next.0 + - @backstage/plugin-search-backend@2.0.3-next.0 + - @backstage/plugin-scaffolder-backend@1.33.1-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.7.2-next.0 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.11-next.0 + - @backstage/plugin-techdocs-backend@2.0.3-next.0 + - @backstage/plugin-app-backend@0.5.3-next.0 + - @backstage/plugin-auth-backend@0.25.1-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.3.4-next.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.9-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.2.11-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.9-next.0 + - @backstage/plugin-devtools-backend@0.5.6-next.0 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.1-next.0 + - @backstage/plugin-kubernetes-backend@0.19.7-next.0 + - @backstage/plugin-notifications-backend@0.5.7-next.0 + - @backstage/plugin-permission-backend@0.7.1-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.9-next.0 + - @backstage/plugin-permission-node@0.10.1-next.0 + - @backstage/plugin-proxy-backend@0.6.3-next.0 + - @backstage/plugin-search-backend-module-catalog@0.3.5-next.0 + - @backstage/plugin-search-backend-module-explore@0.3.3-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.4.3-next.0 + - @backstage/plugin-search-backend-node@1.3.12-next.0 + - @backstage/plugin-signals-backend@0.3.5-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.3-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.1-next.0 + +## e2e-test@0.2.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.7.0-next.0 + +## @internal/frontend@0.0.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + +## @internal/scaffolder@0.0.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/plugin-scaffolder-react@1.16.1-next.0 + +## techdocs-cli-embedded-app@0.2.109-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.13.0-next.0 + - @backstage/plugin-catalog@1.31.0-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + - @backstage/cli@0.32.1 + - @backstage/integration-react@1.2.7 + +## @internal/plugin-todo-list-backend@1.0.40-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 diff --git a/docs/releases/v1.40.0-next.1-changelog.md b/docs/releases/v1.40.0-next.1-changelog.md new file mode 100644 index 0000000000..7f1ae68b01 --- /dev/null +++ b/docs/releases/v1.40.0-next.1-changelog.md @@ -0,0 +1,1922 @@ +# Release v1.40.0-next.1 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.40.0-next.1](https://backstage.github.io/upgrade-helper/?to=1.40.0-next.1) + +## @backstage/plugin-scaffolder-backend@2.0.0-next.1 + +### Major Changes + +- 33394db: **BREAKING CHANGES** + + Removal of deprecated re-exports from module packages. + + The following functions have been re-exported from the `scaffolder-backend` plugin for quite some time, and now it's time to clean them up. They've been moved as follows: + + - `createPublishAzureAction` should be imported from `@backstage/plugin-scaffolder-backend-module-azure` instead. + + - `createPublishBitbucketCloudAction` should be imported from `@backstage/plugin-scaffolder-backend-module-bitbucket-cloud` instead. + + - `createPublishBitbucketServerAction` and `createPublishBitbucketServerPullRequestAction` can be imported from `@backstage/plugin-scaffolder-backend-module-bitbucket-server` instead. + + - `createPublishBitbucketAction` should be imported from `@backstage/plugin-scaffolder-backend-module-bitbucket` instead. + + - `createPublishGerritAction` and `createPublishGerritReviewAction` can be imported from `@backstage/plugin-scaffolder-backend-module-gerrit` instead. + + - `createGithubActionsDispatchAction`, `createGithubDeployKeyAction`, `createGithubEnvironmentAction`, `createGithubIssuesLabelAction`, `CreateGithubPullRequestActionOptions`, `createGithubRepoCreateAction`, `createGithubRepoPushAction`, `createGithubWebhookAction`, and `createPublishGithubAction` can be imported from `@backstage/plugin-scaffolder-backend-module-github` instead. + + - `createPublishGitlabAction` should be imported from `@backstage/plugin-scaffolder-backend-module-gitlab` instead. + + - `ActionContext`. `createTemplateAction`, `executeShellCommand`, `ExecuteShellCommandOptions`, `fetchContents`, `TaskSecrets`, and `TemplateAction` should be imported from `@backstage/plugin-scaffolder-node` instead. + + - `ScaffolderEntitiesProcessor` should be imported from `@backstage/plugin-catalog-backend-module-scaffolder-entity-model` instead. + +- a8fcf04: **BREAKING ALPHA**: The `/alpha` export no longer exports the plugin. Please use `import('@backstage/plugin-scaffolder-backend')` instead as this has been removed. + + **BREAKING CHANGES**: The old `createRouter` function which was used in the old backend system has been removed along with the `RouterOptions` type. + +- 73b94d7: **BREAKING CHANGES** + + The following functions have been re-exported from the `scaffolder-backend` plugin for quite some time, and now it's time to clean them up. They've been moved as follows: + + - `SerializedTask`, `SerializedTaskEvent`, `TaskBroker`, `TaskBrokerDispatchOptions`, `TaskBrokerDispatchResult`, `TaskCompletionState`, `TaskContext`, `TaskEventType`, `TaskStatus`, `TemplateFilter`, and `TemplateGlobal` should be imported from `@backstage/plugin-scaffolder-node` instead. + + - The deprecated `copyWithoutRender` option has been removed from `fetch:template` action. You should rename the option to `copyWithoutTemplating` instead. + +### Minor Changes + +- 73b94d7: **DEPRECATIONS** + + The following types and implementations have been deprecated, either because they're no longer relevant, or because upcoming changes to the `scaffolder-backend` after `2.0.0` will influence the changes to these API surfaces. + + - `CreateWorkerOptions` + - `DatabaseTaskStore` + - `DatabaseTaskStoreOptions` + - `TaskManager` + - `TaskStoreCreateTaskOptions` + - `TaskStoreCreateTaskResult` + - `TaskStoreEmitOptions` + - `TaskStoreListEventsOptions` + - `TaskStoreRecoverTaskOptions` + - `TaskStoreShutDownTaskOptions` + + There is no current path off deprecation, these types are going to be removed and rethought with a better way to define workers in the new backend system. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.10-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.10-next.1 + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.10-next.1 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.10-next.1 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.10-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.11-next.1 + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.7.2-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.2-next.1 + - @backstage/backend-defaults@0.10.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/types@1.2.1 + - @backstage/plugin-bitbucket-cloud-common@0.3.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.9-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1-next.1 + - @backstage/plugin-scaffolder-common@1.5.11 + +## @backstage/canon@0.5.0-next.1 + +### Minor Changes + +- 621fac9: We are updating the default size of the Button component in Canon to be small instead of medium. +- a842554: We set the default size for IconButton in Canon to be small instead of medium. + +## @backstage/backend-app-api@1.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/backend-defaults@0.10.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-app-api@1.2.4-next.1 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/cli-node@0.2.13 + - @backstage/config@1.3.2 + - @backstage/config-loader@1.10.1 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/integration-aws-node@0.1.16 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12-next.1 + - @backstage/plugin-permission-node@0.10.1-next.1 + +## @backstage/backend-dynamic-feature-service@0.7.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/plugin-catalog-backend@2.0.1-next.1 + - @backstage/backend-defaults@0.10.1-next.1 + - @backstage/backend-openapi-utils@0.5.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/cli-common@0.1.15 + - @backstage/cli-node@0.2.13 + - @backstage/config@1.3.2 + - @backstage/config-loader@1.10.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-app-node@0.1.34-next.1 + - @backstage/plugin-events-backend@0.5.3-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1-next.1 + - @backstage/plugin-search-backend-node@1.3.12-next.1 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/backend-openapi-utils@0.5.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/backend-plugin-api@1.4.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1-next.1 + +## @backstage/backend-test-utils@1.6.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-app-api@1.2.4-next.1 + - @backstage/backend-defaults@0.10.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## @backstage/catalog-client@1.10.1-next.0 + +### Patch Changes + +- 22fad0d: Fixed `CatalogClient` error responses for `refreshEntity` and `addLocation`. +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/errors@1.2.7 + +## @backstage/cli@0.32.2-next.0 + +### Patch Changes + +- ce70439: The `BACKSTAGE_CLI_EXPERIMENTAL_BUILD_CACHE` flag has been removed. Existing users are encouraged to switch to `EXPERIMENTAL_RSPACK` instead. +- 1d8f00b: Switched to using the `ModuleFederationPlugin` from `@module-federation/enhanced/rspack` for Rspack, rather than the built-in one. +- 2b9633f: The experimental `FORCE_REACT_DEVELOPMENT` flag has been removed. +- d8c4a54: Only use the caching Jest module loader for frontend packages in order to avoid breaking real ESM module imports. +- 1bab255: Internal refactor to combine alpha `build` and `start` modules. +- 713e957: fix: merge eslint reports when using json format +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/cli-common@0.1.15 + - @backstage/cli-node@0.2.13 + - @backstage/config@1.3.2 + - @backstage/config-loader@1.10.1 + - @backstage/errors@1.2.7 + - @backstage/eslint-plugin@0.1.10 + - @backstage/integration@1.17.0 + - @backstage/release-manifests@0.0.13 + - @backstage/types@1.2.1 + +## @backstage/core-compat-api@0.4.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/version-bridge@1.0.11 + +## @backstage/create-app@0.7.0-next.1 + +### Patch Changes + +- Bumped create-app version. +- Updated dependencies + - @backstage/cli-common@0.1.15 + +## @backstage/dev-utils@1.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/app-defaults@1.6.2 + - @backstage/catalog-model@1.7.4 + - @backstage/core-app-api@1.17.0 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/integration-react@1.2.7 + - @backstage/theme@0.6.6 + +## @backstage/repo-tools@0.14.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/cli-common@0.1.15 + - @backstage/cli-node@0.2.13 + - @backstage/config-loader@1.10.1 + - @backstage/errors@1.2.7 + +## @techdocs/cli@1.9.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/plugin-techdocs-node@1.13.4-next.1 + +## @backstage/plugin-api-docs@0.12.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.31.0-next.1 + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-react@0.4.34 + +## @backstage/plugin-app-backend@0.5.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/config-loader@1.10.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-app-node@0.1.34-next.1 + +## @backstage/plugin-app-node@0.1.34-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config-loader@1.10.1 + +## @backstage/plugin-auth-backend@0.25.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## @backstage/plugin-auth-backend-module-auth0-provider@0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/plugin-auth-backend@0.25.1-next.1 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-auth-backend-module-github-provider@0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## @backstage/plugin-auth-backend-module-google-provider@0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## @backstage/plugin-auth-backend-module-guest-provider@0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.4.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-auth-backend@0.25.1-next.1 + +## @backstage/plugin-auth-backend-module-okta-provider@0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + +## @backstage/plugin-auth-node@0.6.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-catalog@1.31.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/integration-react@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/plugin-scaffolder-common@1.5.11 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-search-react@1.9.1-next.0 + - @backstage/plugin-techdocs-common@0.1.1-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + +## @backstage/plugin-catalog-backend@2.0.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-openapi-utils@0.5.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-events-node@0.4.12-next.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1-next.1 + +## @backstage/plugin-catalog-backend-module-aws@0.4.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-defaults@0.10.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/integration-aws-node@0.1.16 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-kubernetes-common@0.9.5 + +## @backstage/plugin-catalog-backend-module-azure@0.3.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-openapi-utils@0.5.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.4.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/integration@1.17.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.0 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-events-node@0.4.12-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-events-node@0.4.12-next.1 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/plugin-kubernetes-common@0.9.5 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-catalog-backend-module-gitea@0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-catalog-backend-module-github@0.9.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-catalog-backend@2.0.1-next.1 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-events-node@0.4.12-next.1 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-github@0.9.1-next.1 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/plugin-events-node@0.4.12-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab@0.6.7-next.1 + +### Patch Changes + +- 57a0bad: Implement retry for GitLab API calls to handle rate limiting +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-defaults@0.10.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-events-node@0.4.12-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-gitlab@0.6.7-next.1 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@2.0.1-next.1 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-defaults@0.10.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12-next.1 + - @backstage/plugin-permission-common@0.9.0 + +## @backstage/plugin-catalog-backend-module-ldap@0.11.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-catalog-backend-module-logs@0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@2.0.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## @backstage/plugin-catalog-backend-module-msgraph@0.7.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/integration@1.17.0 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-scaffolder-common@1.5.11 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.6.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.8 + - @backstage/plugin-permission-common@0.9.0 + +## @backstage/plugin-catalog-graph@0.4.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/types@1.2.1 + +## @backstage/plugin-catalog-import@0.13.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/integration@1.17.0 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-catalog-node@1.17.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1-next.1 + +## @backstage/plugin-catalog-react@1.18.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/frontend-test-utils@0.3.3-next.0 + - @backstage/integration-react@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-react@0.4.34 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + +## @backstage/plugin-devtools@0.1.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/plugin-devtools-common@0.1.16 + - @backstage/plugin-permission-react@0.4.34 + +## @backstage/plugin-devtools-backend@0.5.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/config-loader@1.10.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-devtools-common@0.1.16 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1-next.1 + +## @backstage/plugin-events-backend@0.5.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.5.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## @backstage/plugin-events-backend-module-aws-sqs@0.4.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## @backstage/plugin-events-backend-module-azure@0.2.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## @backstage/plugin-events-backend-module-bitbucket-server@0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## @backstage/plugin-events-backend-module-gerrit@0.2.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## @backstage/plugin-events-backend-module-github@0.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/integration@1.17.0 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## @backstage/plugin-events-backend-module-gitlab@0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/plugin-events-node@0.4.12-next.1 + +## @backstage/plugin-events-backend-module-google-pubsub@0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## @backstage/plugin-events-backend-test-utils@0.1.45-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.12-next.1 + +## @backstage/plugin-events-node@0.4.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-gateway-backend@1.0.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + +## @backstage/plugin-home@0.8.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.0 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/theme@0.6.6 + - @backstage/plugin-home-react@0.1.26 + +## @backstage/plugin-kubernetes@0.12.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/plugin-kubernetes-react@0.5.7 + - @backstage/plugin-permission-react@0.4.34 + +## @backstage/plugin-kubernetes-backend@0.19.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.16 + - @backstage/types@1.2.1 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/plugin-kubernetes-node@0.3.1-next.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1-next.1 + +## @backstage/plugin-kubernetes-cluster@0.0.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/plugin-kubernetes-react@0.5.7 + - @backstage/plugin-permission-react@0.4.34 + +## @backstage/plugin-kubernetes-node@0.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/types@1.2.1 + - @backstage/plugin-kubernetes-common@0.9.5 + +## @backstage/plugin-notifications@0.5.6-next.1 + +### Patch Changes + +- 1fb5f06: Adds ability for user to turn on/off notifications for specific topics within an origin. +- Updated dependencies + - @backstage/plugin-notifications-common@0.0.9-next.0 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/theme@0.6.6 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-react@0.0.13 + +## @backstage/plugin-notifications-backend@0.5.7-next.1 + +### Patch Changes + +- 1fb5f06: Adds ability for user to turn on/off notifications for specific topics within an origin. +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-notifications-common@0.0.9-next.0 + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/plugin-notifications-node@0.2.16-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12-next.1 + - @backstage/plugin-signals-node@0.1.21-next.1 + +## @backstage/plugin-notifications-backend-module-email@0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-notifications-common@0.0.9-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/plugin-notifications-node@0.2.16-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/integration-aws-node@0.1.16 + - @backstage/types@1.2.1 + +## @backstage/plugin-notifications-backend-module-slack@0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-notifications-common@0.0.9-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/plugin-notifications-node@0.2.16-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-notifications-common@0.0.9-next.0 + +### Patch Changes + +- 1fb5f06: Adds ability for user to turn on/off notifications for specific topics within an origin. +- Updated dependencies + - @backstage/config@1.3.2 + +## @backstage/plugin-notifications-node@0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-notifications-common@0.0.9-next.0 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-signals-node@0.1.21-next.1 + +## @backstage/plugin-org@0.6.40-next.1 + +### Patch Changes + +- 4fe364f: Org plugin support i18n +- Updated dependencies + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/plugin-catalog-common@1.1.4 + +## @backstage/plugin-org-react@0.1.39-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + +## @backstage/plugin-permission-backend@0.7.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1-next.1 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1-next.1 + +## @backstage/plugin-permission-node@0.10.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.0 + +## @backstage/plugin-proxy-backend@0.6.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/types@1.2.1 + - @backstage/plugin-proxy-node@0.1.5-next.1 + +## @backstage/plugin-proxy-node@0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + +## @backstage/plugin-scaffolder@1.31.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/plugin-scaffolder-react@1.16.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/integration@1.17.0 + - @backstage/integration-react@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/plugin-scaffolder-common@1.5.11 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.10-next.1 + +### Patch Changes + +- e89d7b6: Migrating `azure` actions to using the new `zod` schema format +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.11-next.1 + +### Patch Changes + +- 7f710d2: Migrating `bitbucket` actions to use the new `zod` format +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.10-next.1 + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.10-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.10-next.1 + +### Patch Changes + +- ca9fdc0: Migrate `bitbucket-cloud` to new actions format +- 280611d: Fix `bitbucketCloudBranchRestrictions` API calls to accept null to prevent 400 errors for some branch restriction kinds defined. +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.10-next.1 + +### Patch Changes + +- d8169fc: Migrate the actions to the new format +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.10-next.1 + +### Patch Changes + +- 4a86bca: Migrate actions to new format +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.11-next.1 + +### Patch Changes + +- 0b2bbf1: Migrate using new actions format +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/backend-defaults@0.10.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.10-next.1 + +### Patch Changes + +- e24b29b: Migrating to use new format for actions +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.10-next.1 + +### Patch Changes + +- ed41017: Migrate to new actions format +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + +## @backstage/plugin-scaffolder-backend-module-github@0.7.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.9.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.11-next.1 + +### Patch Changes + +- 1fb5f06: Adds ability for user to turn on/off notifications for specific topics within an origin. +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/plugin-notifications-common@0.0.9-next.0 + - @backstage/plugin-notifications-node@0.2.16-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.2.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/plugin-scaffolder-node-test-utils@0.2.3-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-node@0.8.3-next.1 + +### Patch Changes + +- e89d7b6: Use `LoggerService` instead of `Logger`. This is a non-breaking change, as the `LoggerService` is a subset of the `Logger` interface. +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/types@1.2.1 + - @backstage/plugin-scaffolder-common@1.5.11 + +## @backstage/plugin-scaffolder-node-test-utils@0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/backend-test-utils@1.6.0-next.1 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-react@1.16.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/theme@0.6.6 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/plugin-scaffolder-common@1.5.11 + +## @backstage/plugin-search@1.4.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-search-react@1.9.1-next.0 + +## @backstage/plugin-search-backend@2.0.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.1-next.1 + - @backstage/backend-openapi-utils@0.5.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1-next.1 + - @backstage/plugin-search-backend-node@1.3.12-next.1 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-search-backend-module-catalog@0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-search-backend-node@1.3.12-next.1 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-search-backend-module-elasticsearch@1.7.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/integration-aws-node@0.1.16 + - @backstage/plugin-search-backend-node@1.3.12-next.1 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-search-backend-module-explore@0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/plugin-search-backend-node@1.3.12-next.1 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-search-backend-module-pg@0.5.45-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/plugin-search-backend-node@1.3.12-next.1 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/plugin-search-backend-node@1.3.12-next.1 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-search-backend-module-techdocs@0.4.3-next.1 + +### Patch Changes + +- b47ec38: Exports the default document collator for use in document transformation during search indexing. +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-search-backend-node@1.3.12-next.1 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-techdocs-node@1.13.4-next.1 + +## @backstage/plugin-search-backend-node@1.3.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-search-common@1.2.18 + +## @backstage/plugin-signals-backend@0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12-next.1 + - @backstage/plugin-signals-node@0.1.21-next.1 + +## @backstage/plugin-signals-node@0.1.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## @backstage/plugin-techdocs@1.13.0-next.1 + +### Patch Changes + +- 9dde3ba: Improved Keyboard accessibility in techdocs. +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/integration@1.17.0 + - @backstage/integration-react@1.2.7 + - @backstage/theme@0.6.6 + - @backstage/plugin-auth-react@0.1.15 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-search-react@1.9.1-next.0 + - @backstage/plugin-techdocs-common@0.1.1-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.49-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.13.0-next.1 + - @backstage/plugin-catalog@1.31.0-next.1 + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/core-app-api@1.17.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/integration-react@1.2.7 + - @backstage/test-utils@1.7.8 + - @backstage/plugin-search-react@1.9.1-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + +## @backstage/plugin-techdocs-backend@2.0.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.4.3-next.1 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-defaults@0.10.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-techdocs-common@0.1.1-next.0 + - @backstage/plugin-techdocs-node@1.13.4-next.1 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.25-next.1 + +### Patch Changes + +- 9dde3ba: Improved Keyboard accessibility in techdocs. +- Updated dependencies + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/integration@1.17.0 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + +## @backstage/plugin-techdocs-node@1.13.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/integration-aws-node@0.1.16 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-techdocs-common@0.1.1-next.0 + +## @backstage/plugin-user-settings@0.8.23-next.1 + +### Patch Changes + +- ab6d3dd: Add some translation to user-setting plugin +- Updated dependencies + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-app-api@1.17.0 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/theme@0.6.6 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-react@0.0.13 + - @backstage/plugin-user-settings-common@0.0.1 + +## @backstage/plugin-user-settings-backend@0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-defaults@0.10.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-node@0.1.21-next.1 + - @backstage/plugin-user-settings-common@0.0.1 + +## example-app@0.2.110-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/canon@0.5.0-next.1 + - @backstage/cli@0.32.2-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.25-next.1 + - @backstage/plugin-techdocs@1.13.0-next.1 + - @backstage/plugin-org@0.6.40-next.1 + - @backstage/plugin-user-settings@0.8.23-next.1 + - @backstage/plugin-notifications@0.5.6-next.1 + - @backstage/plugin-catalog@1.31.0-next.1 + - @backstage/plugin-catalog-graph@0.4.20-next.1 + - @backstage/plugin-catalog-import@0.13.1-next.1 + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/plugin-home@0.8.9-next.1 + - @backstage/plugin-scaffolder@1.31.1-next.1 + - @backstage/plugin-scaffolder-react@1.16.1-next.1 + - @backstage/app-defaults@1.6.2 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.0 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-app-api@0.11.3-next.0 + - @backstage/integration-react@1.2.7 + - @backstage/theme@0.6.6 + - @backstage/plugin-api-docs@0.12.8-next.1 + - @backstage/plugin-auth-react@0.1.15 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-catalog-unprocessed-entities@0.2.18-next.1 + - @backstage/plugin-devtools@0.1.28-next.1 + - @backstage/plugin-kubernetes@0.12.8-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.26-next.1 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/plugin-search@1.4.27-next.1 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-search-react@1.9.1-next.0 + - @backstage/plugin-signals@0.0.20-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + +## example-app-next@0.0.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/canon@0.5.0-next.1 + - @backstage/cli@0.32.2-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.25-next.1 + - @backstage/plugin-techdocs@1.13.0-next.1 + - @backstage/plugin-org@0.6.40-next.1 + - @backstage/plugin-user-settings@0.8.23-next.1 + - @backstage/plugin-notifications@0.5.6-next.1 + - @backstage/plugin-catalog@1.31.0-next.1 + - @backstage/plugin-catalog-graph@0.4.20-next.1 + - @backstage/plugin-catalog-import@0.13.1-next.1 + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/plugin-home@0.8.9-next.1 + - @backstage/plugin-scaffolder@1.31.1-next.1 + - @backstage/plugin-scaffolder-react@1.16.1-next.1 + - @backstage/app-defaults@1.6.2 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.0 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-app-api@0.11.3-next.0 + - @backstage/frontend-defaults@0.2.3-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/integration-react@1.2.7 + - @backstage/theme@0.6.6 + - @backstage/plugin-api-docs@0.12.8-next.1 + - @backstage/plugin-app@0.1.10-next.0 + - @backstage/plugin-app-visualizer@0.1.20-next.0 + - @backstage/plugin-auth-react@0.1.15 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-catalog-unprocessed-entities@0.2.18-next.1 + - @backstage/plugin-kubernetes@0.12.8-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.26-next.1 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/plugin-search@1.4.27-next.1 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-search-react@1.9.1-next.0 + - @backstage/plugin-signals@0.0.20-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + +## example-backend@0.0.39-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.11-next.1 + - @backstage/plugin-notifications-backend@0.5.7-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.4.3-next.1 + - @backstage/plugin-scaffolder-backend@2.0.0-next.1 + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/plugin-catalog-backend@2.0.1-next.1 + - @backstage/plugin-kubernetes-backend@0.19.7-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.7.2-next.1 + - @backstage/plugin-search-backend-module-catalog@0.3.5-next.1 + - @backstage/plugin-techdocs-backend@2.0.3-next.1 + - @backstage/backend-defaults@0.10.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-app-backend@0.5.3-next.1 + - @backstage/plugin-auth-backend@0.25.1-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.3.4-next.1 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.9-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.3-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.2.11-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.9-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.1-next.1 + - @backstage/plugin-devtools-backend@0.5.6-next.1 + - @backstage/plugin-events-backend@0.5.3-next.1 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.1-next.1 + - @backstage/plugin-permission-backend@0.7.1-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.9-next.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1-next.1 + - @backstage/plugin-proxy-backend@0.6.3-next.1 + - @backstage/plugin-search-backend@2.0.3-next.1 + - @backstage/plugin-search-backend-module-explore@0.3.3-next.1 + - @backstage/plugin-search-backend-node@1.3.12-next.1 + - @backstage/plugin-signals-backend@0.3.5-next.1 + +## e2e-test@0.2.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.7.0-next.1 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + +## @internal/scaffolder@0.0.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.16.1-next.1 + - @backstage/frontend-plugin-api@0.10.3-next.0 + +## techdocs-cli-embedded-app@0.2.109-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.32.2-next.0 + - @backstage/plugin-techdocs@1.13.0-next.1 + - @backstage/plugin-catalog@1.31.0-next.1 + - @backstage/app-defaults@1.6.2 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.0 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/integration-react@1.2.7 + - @backstage/test-utils@1.7.8 + - @backstage/theme@0.6.6 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + +## @internal/plugin-todo-list-backend@1.0.40-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/errors@1.2.7 diff --git a/docs/tooling/local-dev/debugging.md b/docs/tooling/local-dev/debugging.md index 662f04151a..6e12bb9f5f 100644 --- a/docs/tooling/local-dev/debugging.md +++ b/docs/tooling/local-dev/debugging.md @@ -65,7 +65,8 @@ In your `.vscode/launch.json`, add a new entry with the following, "runtimeExecutable": "yarn", // Specifies the runtime to execute the application. In this case, it uses `yarn` to run the script. "args": ["start", "--inspect"], // Arguments passed to the `yarn` command. Here, it runs `yarn start` with the `--inspect` flag to enable debugging. "skipFiles": ["/**"], // Tells the debugger to skip stepping into Node.js internal files during debugging. - "console": "integratedTerminal" // Specifies that the debugger should use the integrated terminal for input/output. + "console": "integratedTerminal", // Specifies that the debugger should use the integrated terminal for input/output. + "experimentalNetworking": "off" // Since Node.js 22.15.0 an additional parameter --experimental-network-inspection is added but currently not supported by Yarn } ] } diff --git a/docs/tutorials/setup-opentelemetry.md b/docs/tutorials/setup-opentelemetry.md index 23cd71b10f..2008e0c5e8 100644 --- a/docs/tutorials/setup-opentelemetry.md +++ b/docs/tutorials/setup-opentelemetry.md @@ -4,7 +4,7 @@ title: Setup OpenTelemetry description: Tutorial to setup OpenTelemetry metrics and traces exporters in Backstage --- -Backstage uses [OpenTelemetery](https://opentelemetry.io/) to instrument its components by reporting traces and metrics. +Backstage uses [OpenTelemetry](https://opentelemetry.io/) to instrument its components by reporting traces and metrics. This tutorial shows how to setup exporters in your Backstage backend package. For demonstration purposes we will use a Prometheus exporter, but you can adjust your solution to use another one that suits your needs; see for example the article on [OTLP exporters](https://opentelemetry.io/docs/instrumentation/js/exporters/). @@ -26,7 +26,7 @@ yarn --cwd packages/backend add \ In your `packages/backend/src` folder, create an `instrumentation.js` file. -```typescript title="in packages/backend/src/instrumentation.js" +```js title="in packages/backend/src/instrumentation.js" // Prevent from running more than once (due to worker threads) const { isMainThread } = require('node:worker_threads'); @@ -52,6 +52,42 @@ if (isMainThread) { You probably won't need all of the instrumentation inside `getNodeAutoInstrumentations()` so make sure to check the [documentation](https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node) and tweak it properly. +### Views + +The default histogram buckets for OpenTelemetry are in milliseconds, but the histograms that are created for Catalog processing emit metrics in second. You might want to adjust this to what fits your need. To do this you can use the [Views feature](https://opentelemetry.io/docs/concepts/signals/metrics/#views) like this: + +```js +const prometheus = new PrometheusExporter(); +const sdk = new NodeSDK({ + metricReader: prometheus, + views: [ + new View({ + instrumentName: 'catalog.test', + aggregation: new ExplicitBucketHistogramAggregation([ + 0.01, 0.1, 0.5, 1, 5, 10, 25, 50, 100, 500, 1000, + ]), + }), + ], +}); +``` + +The above will make all the histogram buckets use the same config. If you would like to take a more targeted approach you can do this: + +```js +const prometheus = new PrometheusExporter(); +const sdk = new NodeSDK({ + metricReader: prometheus, + views: [ + new View({ + instrumentName: 'catalog.test', + aggregation: new ExplicitBucketHistogramAggregation([ + 0, 0.01, 0.05, 0.1, 0.25, 0.5, 1, 2, 5, 10, 30, 60, 120, 300, 1000, + ]), + }), + ], +}); +``` + ## Local Development Setup It's important to setup the NodeSDK and the automatic instrumentation **before** @@ -67,13 +103,13 @@ For local development, you can add the required flag in your `packages/backend/p ... ``` -You can now start your Backstage instance as usual, using `yarn start`. +You can now start your Backstage instance as usual, using `yarn start` and you'll be able to see your metrics at: ## Production Setup In your `.dockerignore`, add this line: -``` +```text !packages/backend/src/instrumentation.js ``` diff --git a/microsite/blog/2025-05-12-from-zero-to-maintainer-my-opensource-journey-with-backstage.mdx b/microsite/blog/2025-05-12-from-zero-to-maintainer-my-opensource-journey-with-backstage.mdx new file mode 100644 index 0000000000..96a4a8e140 --- /dev/null +++ b/microsite/blog/2025-05-12-from-zero-to-maintainer-my-opensource-journey-with-backstage.mdx @@ -0,0 +1,63 @@ +--- +title: 'From Zero to Maintainer: My Open Source Journey with Backstage' +author: Kashish Mittal, Red Hat +authorURL: https://github.com/04kash +authorImageURL: https://avatars.githubusercontent.com/u/113269381?v=4 +--- + +My path to becoming a maintainer in the Backstage project wasn’t paved with years of prior open source experience. As a student interning at Red Hat, I started with a relatively fresh perspective. This is the story of that journey – how I navigated the complexities of Backstage, with the support of its community, allowing me to grow and learn, and how open source can empower anyone to do the same. Whether you’re new to open source or a seasoned developer, I believe you’ll find something valuable here. For those just starting out, I hope this inspires you to take that first step. And for experienced developers, I hope this offers a fresh perspective – a look at how a new contributor navigates a complex project, and perhaps, a reminder of our own beginnings. +This journey wasn’t made in a single leap, but through a series of progressive stages. I’ll walk you through these stages, highlighting the key learnings and contributions along the way. + +![The Stages of my Journey](assets/2025-05-12/stages.png) +{/* truncate */} + +## Stage 1: Laying the foundation + +![Getting Familiar with the Backstage Tech Stack](assets/2025-05-12/tech_stack.png) + +When I first encountered Backstage, I wasn’t deeply familiar with the tech stack it relied on — Node.js, TypeScript, and React. My only exposure previously had been through hackathons, where the focus is speed over deep understanding. Another major hurdle was Git. Up until that point, I had mainly used GitHub Desktop, rarely venturing into the command line or advanced Git concepts. Realizing that effective contribution required a deeper understanding, I dedicated a week to intensively reviewing these core technologies. + +But that wasn’t all. I wanted to see Backstage in action, to really grasp what it was all about. That’s when I came across the [Spotify Learning Path for Backstage](https://backstage.spotify.com/learn/). Specifically, the [“Standing up Backstage”](https://backstage.spotify.com/learn/standing-up-backstage/) section was incredibly useful. It walked me through setting up a local running instance with a Postgres database and GitHub authentication. From there, I explored the Backstage documentation and experimented with adding features to my local instance. Honestly, for me, this stage was all about building confidence. It was about facing my knowledge gaps head-on and taking the time to learn. + +## Stage 2: First contributions + +![First contributions to Backstage: learning the process and connecting with the community](assets/2025-05-12/first_contributions.png) + +With my foundational skills in place, it was time to take the leap and make my first contributions to Backstage. [My first pull request](https://github.com/backstage/community-plugins/pull/497) (PR) was quite simple — fixing broken links in the documentation. This seemingly minor fix was my first step, and it paved the way for more substantial contributions. It helped me to get comfortable with the contributing process, learn how to submit a PR, and understand the review workflow. After that, I started looking for open issues in the [community-plugins repository](https://github.com/backstage/community-plugins). I found an issue about [linking npm releases of plugins to specific Git commits](https://github.com/backstage/community-plugins/issues/489) — a task that pushed me outside my comfort zone once again. At the time, I knew very little about GitHub Actions and workflows. Learning on the fly, I relied heavily on guidance from maintainers who were incredibly welcoming. +This experience was really helpful, not just for the task itself, but also because it introduced me to key community resources like the [Backstage Discord](https://discord.com/invite/backstage-687207715902193673) and [Special Interest Groups](https://github.com/backstage/community/tree/main/sigs) (or SIGs for short). While the content of the pull requests in this stage was certainly useful, the real value came from learning the project’s workflows and community processes. + +## Stage 3: Making substantial contributions + +As my confidence grew, so did the complexity of the contributions that I took on. Two of my first feature-related contributions were related to Catalog Permissions and the LDAP Processor. +First, with Catalog Permissions, I noticed that two endpoints in the Catalog plugin, `/analyze-location` and `/validate-entity` weren’t restricted by permissions, posing a security risk. To address this, I needed a deep understanding of Backstage’s permissions system. I spent a lot of time reading through documentation and existing code, and asking questions on Discord. When I had [a PR](https://github.com/backstage/backstage/pull/25924) ready, I went to the Framework SIG meetings to discuss my implementation and to make sure that I was on the same page as the maintainers. Following this process, my PR was successfully merged! +Shortly after, I tackled an open issue with the [LDAP processor](https://github.com/backstage/backstage/issues/26225). This issue highlighted a problem where Backstage couldn’t correctly map user-group relationships with certain LDAP server configurations that used attribute names that were different from the ones hardcoded within the plugin. I implemented [a solution](https://github.com/backstage/backstage/pull/26511) that allowed users to override these default values. Testing against a real LDAP instance wasn’t something I could do locally, but the original issue reporter stepped in, validating my changes in their environment. It was a beautiful example of open source collaboration in action. + +![My first commits in the backstage CHANGELOG](assets/2025-05-12/changelog.png) +_My first commits in the backstage CHANGELOG_ + +These two tasks were a significant step up from my initial contributions. They required a deeper understanding of Backstage’s architecture and a greater level of problem-solving. Seeing my commits in the [CHANGELOG](https://backstage.io/docs/releases/v1.31.0/#new-catalog-permissions) was an accomplishment I was proud of, and it solidified my confidence so I could take on even more substantial roles within the Backstage community. + +## Stage 4: Becoming an active community member + +![Engaging with the Open Source Community at the CNCF meetup in Toronto](assets/2025-05-12/cncf_1.jpeg) +_(Source: https://www.linkedin.com/posts/mwijay_you-totally-missed-last-nights-first-cncf-ugcPost-7311005210628235264-8BES)_ + +![Engaging with the Open Source Community at the CNCF meetup in Toronto](assets/2025-05-12/cncf_2.png) +_Engaging with the Open Source Community at the CNCF meetup in Toronto (Source: https://community.cncf.io/events/details/cncf-cloud-native-toronto-presents-cncf-2025-kickoff-at-shopify/)_ + +One of the ways I got more involved was by helping with the maintenance of the [community-plugins repository](https://github.com/backstage/community-plugins). For instance, I helped address a security vulnerability — specifically, that the `@backstage/backend-common package` relied on a vulnerable version of `jsonpath-plus` through `@kubernetes/client-node`. I proactively removed this vulnerable dependency from eight plugins, effectively safeguarding those plugins from potential risks. I also focused on improving dependency management by implementing Knip reports. These reports help us identify and remove unused dependencies, keeping the repository clean and efficient. + +Beyond repository maintenance, I also began focusing on community engagement. I began reviewing pull requests regularly, helping maintain code quality and lightening the load on other maintainers. This not only deepened my understanding of the codebase but also exposed me to new features and patterns across the ecosystem. I actively participated in SIG meetings and offered guidance to fellow contributors on Discord. + +This marked a turning point in my journey, as I transitioned from a contributor to an active community member. Eventually, this growing involvement led to me becoming a maintainer for the community-plugins repository. It was a fulfilling step forward, and it felt rewarding to be trusted with more responsibility in guiding the project. + +## What’s next: future steps as a maintainer + +![Presenting "From Zero to Contributor: My Open Source Journey with Backstage" at the CNCF 2025 Kickoff event at Shopify, Toronto](assets/2025-05-12/cncf_3.jpeg) +_Presenting "From Zero to Contributor: My Open Source Journey with Backstage" at the CNCF 2025 Kickoff event at Shopify, Toronto (Source: https://www.linkedin.com/posts/mwijay_you-totally-missed-last-nights-first-cncf-ugcPost-7311005210628235264-8BES)_ + +As a maintainer, my focus is on supporting others in their open source journey and improving the overall contributor experience. I actively review PRs, guide new contributors through constructive reviews, and point them to good first issues to help them get started. +Furthermore, I aim to actively contribute to the community-plugins SIG meetings, offering suggestions and participating in discussions to help guide the project’s direction. +Ultimately, my goal is to help the community-plugins repository thrive and become an even more valuable resource for the Backstage community. I’m excited to collaborate with fellow maintainers and contributors to achieve this vision. + +My journey with Backstage has been incredibly rewarding, and I’m grateful for the opportunities, support, and growth the community has given me. I’m excited to continue contributing and to see where this journey takes me next. diff --git a/microsite/blog/assets/2025-05-12/changelog.png b/microsite/blog/assets/2025-05-12/changelog.png new file mode 100644 index 0000000000..4a97d31a55 Binary files /dev/null and b/microsite/blog/assets/2025-05-12/changelog.png differ diff --git a/microsite/blog/assets/2025-05-12/cncf_1.jpeg b/microsite/blog/assets/2025-05-12/cncf_1.jpeg new file mode 100644 index 0000000000..7157be6805 Binary files /dev/null and b/microsite/blog/assets/2025-05-12/cncf_1.jpeg differ diff --git a/microsite/blog/assets/2025-05-12/cncf_2.png b/microsite/blog/assets/2025-05-12/cncf_2.png new file mode 100644 index 0000000000..1a7766d665 Binary files /dev/null and b/microsite/blog/assets/2025-05-12/cncf_2.png differ diff --git a/microsite/blog/assets/2025-05-12/cncf_3.jpeg b/microsite/blog/assets/2025-05-12/cncf_3.jpeg new file mode 100644 index 0000000000..cf9070a35b Binary files /dev/null and b/microsite/blog/assets/2025-05-12/cncf_3.jpeg differ diff --git a/microsite/blog/assets/2025-05-12/first_contributions.png b/microsite/blog/assets/2025-05-12/first_contributions.png new file mode 100644 index 0000000000..26ecf77565 Binary files /dev/null and b/microsite/blog/assets/2025-05-12/first_contributions.png differ diff --git a/microsite/blog/assets/2025-05-12/stages.png b/microsite/blog/assets/2025-05-12/stages.png new file mode 100644 index 0000000000..1d6aea0b64 Binary files /dev/null and b/microsite/blog/assets/2025-05-12/stages.png differ diff --git a/microsite/blog/assets/2025-05-12/tech_stack.png b/microsite/blog/assets/2025-05-12/tech_stack.png new file mode 100644 index 0000000000..6af9194488 Binary files /dev/null and b/microsite/blog/assets/2025-05-12/tech_stack.png differ diff --git a/microsite/data/plugins/daily-weather-update.yaml b/microsite/data/plugins/daily-weather-update.yaml new file mode 100644 index 0000000000..4118a4d5d6 --- /dev/null +++ b/microsite/data/plugins/daily-weather-update.yaml @@ -0,0 +1,10 @@ +--- +title: Daily Weather +author: Infosys Limited +authorUrl: https://github.com/Infosys +category: Utility +description: The Daily Weather Plugin for Backstage provides real-time weather information for a specified location using the Weatherstack API. +documentation: https://github.com/Infosys/daily-weather-plugin/tree/main/plugins/weather +iconUrl: https://github.com/Infosys/daily-weather-plugin/blob/main/plugins/weather/src/docs/plugin-logo.png?raw=true +npmPackageName: '@infosys_ltd/daily-weather-plugin' +addedDate: '2025-04-16' diff --git a/microsite/data/plugins/wheel-of-names.yaml b/microsite/data/plugins/wheel-of-names.yaml new file mode 100644 index 0000000000..0377742ab0 --- /dev/null +++ b/microsite/data/plugins/wheel-of-names.yaml @@ -0,0 +1,10 @@ +--- +title: Wheel of Names +author: intive +authorUrl: https://www.intive.com/ +category: Utility +description: This plugin provides a customizable spinning wheel that can be used for random selection, decision making, or just for fun in your Backstage instance. +documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/wheel-of-names/plugins/wheel-of-names/README.md +iconUrl: /img/wheel-of-names.svg +npmPackageName: '@backstage-community/plugin-wheel-of-names' +addedDate: '2025-05-21' diff --git a/microsite/docusaurus.config.ts b/microsite/docusaurus.config.ts index 71331710d5..d71483ab8f 100644 --- a/microsite/docusaurus.config.ts +++ b/microsite/docusaurus.config.ts @@ -343,13 +343,14 @@ const config: Config = { ], colorMode: { + disableSwitch: false, defaultMode: 'dark', - disableSwitch: true, }, navbar: { logo: { alt: 'Backstage Software Catalog and Developer Platform', - src: 'img/logo.svg', + src: 'img/logo-black.svg', + srcDark: 'img/logo.svg', }, items: [ { diff --git a/microsite/src/components/bannerSection/bannerSection.module.scss b/microsite/src/components/bannerSection/bannerSection.module.scss index 64f4967d11..f6fe0536dc 100644 --- a/microsite/src/components/bannerSection/bannerSection.module.scss +++ b/microsite/src/components/bannerSection/bannerSection.module.scss @@ -38,13 +38,12 @@ } &:global(.greyBackground) { - background: linear-gradient(90.49deg, #121212 15.36%, #282828 70.44%); + background: var(--ifm-grey-background); } &:global(.greenGradientBackground) { - background: linear-gradient(70.44deg, #121212 55%, #5d817b); + background: var(--ifm-green-gradient-background); } - &:global(.greenBottomGradientBackground) { background: linear-gradient( 178.64deg, diff --git a/microsite/src/pages/community/index.tsx b/microsite/src/pages/community/index.tsx index 8c1736e1e9..9770c91e8e 100644 --- a/microsite/src/pages/community/index.tsx +++ b/microsite/src/pages/community/index.tsx @@ -5,6 +5,8 @@ import { BannerSectionGrid } from '@site/src/components/bannerSection/bannerSect import Layout from '@theme/Layout'; import { clsx } from 'clsx'; import React from 'react'; +import ThemedImage from '@theme/ThemedImage'; +import useBaseUrl from '@docusaurus/useBaseUrl'; import { ContentBlock } from '../../components/contentBlock/contentBlock'; import communityStyles from './community.module.scss'; @@ -83,47 +85,47 @@ const Community = () => { { name: 'Frontside Software', url: 'https://frontside.com/backstage/', - logo: 'img/partner-logo-frontside.png', + logo: 'img/partner-logo-frontside', }, { name: 'RedHat', url: 'https://developers.redhat.com/rhdh', - logo: 'img/partner-logo-redhat.png', + logo: 'img/partner-logo-redhat', }, { name: 'Roadie', url: 'https://roadie.io/', - logo: 'img/partner-logo-roadie.png', + logo: 'img/partner-logo-roadie', }, { name: 'solo.io', url: 'https://www.solo.io/spotlight/', - logo: 'img/partner-logo-solo.png', + logo: 'img/partner-logo-solo', }, { name: 'ThoughtWorks', url: 'https://www.thoughtworks.com/about-us/partnerships/technology/backstage-by-spotify', - logo: 'img/partner-logo-thoughtworks.png', + logo: 'img/partner-logo-thoughtworks', }, { name: 'VMWare', url: 'https://tanzu.vmware.com/developer-portal', - logo: 'img/partner-logo-tanzubybroadcom.png', + logo: 'img/partner-logo-tanzubybroadcom', }, { name: 'StatusNeo', url: 'https://statusneo.com/backstage', - logo: 'img/partner-logo-statusneo.png', + logo: 'img/partner-logo-statusneo', }, { name: 'Alauda', url: 'https://www.alauda.io/community/169249', - logo: 'img/partner-logo-alauda.png', + logo: 'img/partner-logo-alauda', }, { name: 'Liatrio', url: 'https://www.liatrio.com/service-offerings/backstage', - logo: 'img/partner-logo-liatrio.png', + logo: 'img/partner-logo-liatrio', }, ]; //#endregion @@ -233,7 +235,13 @@ const Community = () => { {partners.map(({ name, url, logo }, index) => (
- {name} +
))} diff --git a/microsite/src/pages/home/_home.tsx b/microsite/src/pages/home/_home.tsx index c0cf39ad9c..00691e8a75 100644 --- a/microsite/src/pages/home/_home.tsx +++ b/microsite/src/pages/home/_home.tsx @@ -7,9 +7,10 @@ import { ContentBlock } from '@site/src/components/contentBlock/contentBlock'; import Layout from '@theme/Layout'; import { clsx } from 'clsx'; import React, { useState } from 'react'; - +import ThemedImage from '@theme/ThemedImage'; import homeStyles from './home.module.scss'; import { HubSpotNewAdoptersForm } from './_hubSpotNewAdoptersForm'; +import useBaseUrl from '@docusaurus/useBaseUrl'; const hiddenNewsletterBannerKey = 'hiddenNewsletterBanner'; @@ -528,10 +529,13 @@ const HomePage = () => { incubation project - CNCF Logo diff --git a/microsite/src/theme/customTheme.scss b/microsite/src/theme/customTheme.scss index 51615ed0dc..707acb4073 100644 --- a/microsite/src/theme/customTheme.scss +++ b/microsite/src/theme/customTheme.scss @@ -7,6 +7,61 @@ --ifm-color-primary-darker: #2e9e8a; --ifm-color-primary-darkest: #268271; } + +html[data-theme='dark'] { + --ifm-grey-background: linear-gradient( + 90.49deg, + #121212 15.36%, + #282828 70.44% + ); + --ifm-green-gradient-background: linear-gradient( + 70.44deg, + #121212 55%, + #5d817b + ); + --bullet-line-color: linear-gradient( + 89.75deg, + #9bf0e1 -1.5%, + rgba(155, 240, 225, 0) 111.48% + ); + --ifm-button-border-color: var(--ifm-color-primary-darker); + + /* Logo */ + --backstage-logo: url(/img/logo.svg); +} + +html[data-theme='light'] { + --ifm-color-primary: var(--ifm-color-primary-darkest); + --ifm-grey-background: linear-gradient( + 90.49deg, + #e0e0e0 15.36%, + #f5f5f5 70.44% + ); + --ifm-green-gradient-background: linear-gradient( + 70.44deg, + #e0f7f4 55%, + #a3d9d2 + ); + --ifm-breadcrumb-item-background-active: #f8f8f8; + --bullet-line-color: linear-gradient( + 89.75deg, + var(--ifm-color-primary-dark) -1.5%, + rgba(155, 240, 225, 0) 111.48% + ); + --ifm-font-color-base: #000000; + --ifm-menu-color: #6E; + --feedback-button-dark-text-color: #f8f8f8; + + /* Active Colors */ + --ifm-breadcrumb-color-active: var(--ifm-color-primary-darkest); + --ifm-menu-color-active: var(--ifm-color-primary-darkest); + --ifm-link-color: var(--ifm-color-primary-darkest); + --ifm-link-color-hover: var(--ifm-color-primary-darkest); + + /* Logo */ + --backstage-logo: url(/img/logo-black.svg); +} + // for docs and releases #__docusaurus { .theme-doc-markdown { @@ -46,7 +101,7 @@ } } .footerLogo { - background-image: url(/img/logo.svg); + background-image: var(--backstage-logo); background-repeat: no-repeat; height: 40px; width: 180px; @@ -86,11 +141,7 @@ /* #region Utility component styles */ .bulletLine { height: 3px; - background: linear-gradient( - 89.75deg, - #9bf0e1 -1.5%, - rgba(155, 240, 225, 0) 111.48% - ); + background: var(--bullet-line-color); } /* #endregion */ diff --git a/microsite/static/img/logo-black.svg b/microsite/static/img/logo-black.svg new file mode 100644 index 0000000000..b5ff591d1b --- /dev/null +++ b/microsite/static/img/logo-black.svg @@ -0,0 +1 @@ +05 Logo_Black \ No newline at end of file diff --git a/microsite/static/img/partner-logo-alauda-blk.png b/microsite/static/img/partner-logo-alauda-blk.png new file mode 100644 index 0000000000..3d93eb1444 Binary files /dev/null and b/microsite/static/img/partner-logo-alauda-blk.png differ diff --git a/microsite/static/img/partner-logo-frontside-blk.png b/microsite/static/img/partner-logo-frontside-blk.png new file mode 100644 index 0000000000..16b14ac588 Binary files /dev/null and b/microsite/static/img/partner-logo-frontside-blk.png differ diff --git a/microsite/static/img/partner-logo-liatrio-blk.png b/microsite/static/img/partner-logo-liatrio-blk.png new file mode 100644 index 0000000000..a3b599c98c Binary files /dev/null and b/microsite/static/img/partner-logo-liatrio-blk.png differ diff --git a/microsite/static/img/partner-logo-redhat-blk.png b/microsite/static/img/partner-logo-redhat-blk.png new file mode 100644 index 0000000000..8279127da9 Binary files /dev/null and b/microsite/static/img/partner-logo-redhat-blk.png differ diff --git a/microsite/static/img/partner-logo-roadie-blk.png b/microsite/static/img/partner-logo-roadie-blk.png new file mode 100644 index 0000000000..a837d3f6f5 Binary files /dev/null and b/microsite/static/img/partner-logo-roadie-blk.png differ diff --git a/microsite/static/img/partner-logo-solo-blk.png b/microsite/static/img/partner-logo-solo-blk.png new file mode 100644 index 0000000000..d9da222f22 Binary files /dev/null and b/microsite/static/img/partner-logo-solo-blk.png differ diff --git a/microsite/static/img/partner-logo-statusneo-blk.png b/microsite/static/img/partner-logo-statusneo-blk.png new file mode 100644 index 0000000000..78fb9bd10e Binary files /dev/null and b/microsite/static/img/partner-logo-statusneo-blk.png differ diff --git a/microsite/static/img/partner-logo-tanzubybroadcom-blk.png b/microsite/static/img/partner-logo-tanzubybroadcom-blk.png new file mode 100644 index 0000000000..0b66842336 Binary files /dev/null and b/microsite/static/img/partner-logo-tanzubybroadcom-blk.png differ diff --git a/microsite/static/img/partner-logo-thoughtworks-blk.png b/microsite/static/img/partner-logo-thoughtworks-blk.png new file mode 100644 index 0000000000..a9a4894ccf Binary files /dev/null and b/microsite/static/img/partner-logo-thoughtworks-blk.png differ diff --git a/microsite/static/img/wheel-of-names.svg b/microsite/static/img/wheel-of-names.svg new file mode 100644 index 0000000000..d6f19a06fb --- /dev/null +++ b/microsite/static/img/wheel-of-names.svg @@ -0,0 +1,4 @@ + + + + diff --git a/package.json b/package.json index 35c1d8e24b..3cc5755ccb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "1.39.0", + "version": "1.40.0-next.1", "backstage": { "cli": { "new": { diff --git a/packages/app-next-example-plugin/CHANGELOG.md b/packages/app-next-example-plugin/CHANGELOG.md index aca60c550b..35f6bf7dcb 100644 --- a/packages/app-next-example-plugin/CHANGELOG.md +++ b/packages/app-next-example-plugin/CHANGELOG.md @@ -1,5 +1,12 @@ # app-next-example-plugin +## 0.0.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + ## 0.0.23 ### Patch Changes diff --git a/packages/app-next-example-plugin/package.json b/packages/app-next-example-plugin/package.json index 71622bff6a..d2ac22833e 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.23", + "version": "0.0.24-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 ab9a0067db..4d446d4d5d 100644 --- a/packages/app-next/CHANGELOG.md +++ b/packages/app-next/CHANGELOG.md @@ -1,5 +1,86 @@ # example-app-next +## 0.0.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/canon@0.5.0-next.1 + - @backstage/cli@0.32.2-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.25-next.1 + - @backstage/plugin-techdocs@1.13.0-next.1 + - @backstage/plugin-org@0.6.40-next.1 + - @backstage/plugin-user-settings@0.8.23-next.1 + - @backstage/plugin-notifications@0.5.6-next.1 + - @backstage/plugin-catalog@1.31.0-next.1 + - @backstage/plugin-catalog-graph@0.4.20-next.1 + - @backstage/plugin-catalog-import@0.13.1-next.1 + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/plugin-home@0.8.9-next.1 + - @backstage/plugin-scaffolder@1.31.1-next.1 + - @backstage/plugin-scaffolder-react@1.16.1-next.1 + - @backstage/app-defaults@1.6.2 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.0 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-app-api@0.11.3-next.0 + - @backstage/frontend-defaults@0.2.3-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/integration-react@1.2.7 + - @backstage/theme@0.6.6 + - @backstage/plugin-api-docs@0.12.8-next.1 + - @backstage/plugin-app@0.1.10-next.0 + - @backstage/plugin-app-visualizer@0.1.20-next.0 + - @backstage/plugin-auth-react@0.1.15 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-catalog-unprocessed-entities@0.2.18-next.1 + - @backstage/plugin-kubernetes@0.12.8-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.26-next.1 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/plugin-search@1.4.27-next.1 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-search-react@1.9.1-next.0 + - @backstage/plugin-signals@0.0.20-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + +## 0.0.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.31.1-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.18-next.0 + - @backstage/plugin-app-visualizer@0.1.20-next.0 + - @backstage/plugin-catalog-import@0.13.1-next.0 + - @backstage/plugin-catalog-graph@0.4.20-next.0 + - @backstage/plugin-notifications@0.5.6-next.0 + - @backstage/plugin-user-settings@0.8.23-next.0 + - @backstage/plugin-kubernetes@0.12.8-next.0 + - @backstage/plugin-api-docs@0.12.8-next.0 + - @backstage/plugin-techdocs@1.13.0-next.0 + - @backstage/plugin-catalog@1.31.0-next.0 + - @backstage/plugin-signals@0.0.20-next.0 + - @backstage/plugin-search@1.4.27-next.0 + - @backstage/plugin-home@0.8.9-next.0 + - @backstage/plugin-app@0.1.10-next.0 + - @backstage/plugin-org@0.6.40-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + - @backstage/frontend-defaults@0.2.3-next.0 + - @backstage/frontend-app-api@0.11.3-next.0 + - @backstage/canon@0.5.0-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + - @backstage/plugin-scaffolder-react@1.16.1-next.0 + - @backstage/cli@0.32.1 + - @backstage/plugin-catalog-react@1.18.1-next.0 + - @backstage/plugin-search-react@1.9.1-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.25-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.26-next.0 + - @backstage/integration-react@1.2.7 + ## 0.0.23 ### Patch Changes diff --git a/packages/app-next/app-config.yaml b/packages/app-next/app-config.yaml index 0e6dd0a356..da233b0188 100644 --- a/packages/app-next/app-config.yaml +++ b/packages/app-next/app-config.yaml @@ -8,6 +8,20 @@ app: catalog.createComponent: catalog-import.importPage org.catalogIndex: catalog.catalogIndex + pluginOverrides: + - match: + pluginId: pages + info: + description: 'This description was overridden in packages/app-next/app-config.yaml' + - match: + pluginId: /^catalog(-.*)?$/ + info: + ownerEntityRefs: [cubic-belugas] + - match: + packageName: '@backstage/plugin-scaffolder' + info: + ownerEntityRefs: [cubic-belugas] + extensions: # - 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 9d8e7665af..d094ca942f 100644 --- a/packages/app-next/package.json +++ b/packages/app-next/package.json @@ -1,6 +1,6 @@ { "name": "example-app-next", - "version": "0.0.23", + "version": "0.0.24-next.1", "backstage": { "role": "frontend" }, diff --git a/packages/app-next/src/App.tsx b/packages/app-next/src/App.tsx index 0ecb46e06a..43cc63d1a4 100644 --- a/packages/app-next/src/App.tsx +++ b/packages/app-next/src/App.tsx @@ -43,6 +43,7 @@ import kubernetesPlugin from '@backstage/plugin-kubernetes/alpha'; import { convertLegacyPlugin } from '@backstage/core-compat-api'; import { convertLegacyPageExtension } from '@backstage/core-compat-api'; import { convertLegacyEntityContentExtension } from '@backstage/plugin-catalog-react/alpha'; +import { pluginInfoResolver } from './pluginInfoResolver'; /* @@ -132,6 +133,7 @@ const app = createApp({ customHomePageModule, ...collectedLegacyPlugins, ], + pluginInfoResolver, /* Handled through config instead */ // bindRoutes({ bind }) { // bind(pagesPlugin.externalRoutes, { pageX: pagesPlugin.routes.pageX }); diff --git a/packages/app-next/src/examples/pagesPlugin.tsx b/packages/app-next/src/examples/pagesPlugin.tsx index ddd080c28b..33fc7d8c0b 100644 --- a/packages/app-next/src/examples/pagesPlugin.tsx +++ b/packages/app-next/src/examples/pagesPlugin.tsx @@ -21,7 +21,10 @@ import { createExternalRouteRef, useRouteRef, PageBlueprint, + FrontendPluginInfo, + useAppNode, } from '@backstage/frontend-plugin-api'; +import { useEffect, useState } from 'react'; import { Route, Routes } from 'react-router-dom'; const indexRouteRef = createRouteRef(); @@ -36,6 +39,22 @@ export const pageXRouteRef = createRouteRef(); // path: '/page2', // }); +function PluginInfo() { + const node = useAppNode(); + const [info, setInfo] = useState(undefined); + + useEffect(() => { + node?.spec.source?.info().then(setInfo); + }, [node]); + + return ( +
+

Plugin Info

+
{JSON.stringify(info, null, 2)}
+
+ ); +} + const IndexPage = PageBlueprint.make({ name: 'index', params: { @@ -64,6 +83,7 @@ const IndexPage = PageBlueprint.make({
Settings
+ ); }; @@ -139,6 +159,10 @@ export const pagesPlugin = createFrontendPlugin({ // // OR // // 'page1' // }, + info: { + packageJson: () => import('../../package.json'), + manifest: () => import('../../catalog-info.yaml'), + }, routes: { page1: page1RouteRef, pageX: pageXRouteRef, diff --git a/packages/app-next/src/pluginInfoResolver.ts b/packages/app-next/src/pluginInfoResolver.ts new file mode 100644 index 0000000000..12f730875c --- /dev/null +++ b/packages/app-next/src/pluginInfoResolver.ts @@ -0,0 +1,52 @@ +/* + * 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 { Entity } from '@backstage/catalog-model'; +import { FrontendPluginInfoResolver } from '@backstage/frontend-app-api'; + +// This file shows an example of what it looks like to extend the plugin info +// resolution with custom logic and fields. In this case we're reading the +// `spec.type` field from the plugin manifest (catalog-info.yaml). +// +// Using module augmentation we extend the `FrontendPluginInfo` interface to +// include our custom fields. This makes these fields available throughout our project. + +declare module '@backstage/frontend-plugin-api' { + export interface FrontendPluginInfo { + /** + * **DO NOT USE** + * + * This field is added in the example app to showcase module augmentation + * for extending the plugin info in internal apps. It only exists as an + * example in this project. + */ + exampleFieldDoNotUse?: string; + } +} + +export const pluginInfoResolver: FrontendPluginInfoResolver = async ctx => { + const manifest = (await ctx.manifest?.()) as Entity | undefined; + const { info: defaultInfo } = await ctx.defaultResolver({ + packageJson: await ctx.packageJson(), + manifest: manifest, + }); + return { + info: { + ...defaultInfo, + exampleFieldDoNotUse: manifest?.spec?.type?.toString(), + }, + }; +}; diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index b59945d5cd..c27e814e51 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,78 @@ # example-app +## 0.2.110-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/canon@0.5.0-next.1 + - @backstage/cli@0.32.2-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.25-next.1 + - @backstage/plugin-techdocs@1.13.0-next.1 + - @backstage/plugin-org@0.6.40-next.1 + - @backstage/plugin-user-settings@0.8.23-next.1 + - @backstage/plugin-notifications@0.5.6-next.1 + - @backstage/plugin-catalog@1.31.0-next.1 + - @backstage/plugin-catalog-graph@0.4.20-next.1 + - @backstage/plugin-catalog-import@0.13.1-next.1 + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/plugin-home@0.8.9-next.1 + - @backstage/plugin-scaffolder@1.31.1-next.1 + - @backstage/plugin-scaffolder-react@1.16.1-next.1 + - @backstage/app-defaults@1.6.2 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.0 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-app-api@0.11.3-next.0 + - @backstage/integration-react@1.2.7 + - @backstage/theme@0.6.6 + - @backstage/plugin-api-docs@0.12.8-next.1 + - @backstage/plugin-auth-react@0.1.15 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-catalog-unprocessed-entities@0.2.18-next.1 + - @backstage/plugin-devtools@0.1.28-next.1 + - @backstage/plugin-kubernetes@0.12.8-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.26-next.1 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/plugin-search@1.4.27-next.1 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-search-react@1.9.1-next.0 + - @backstage/plugin-signals@0.0.20-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + +## 0.2.110-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.31.1-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.18-next.0 + - @backstage/plugin-catalog-import@0.13.1-next.0 + - @backstage/plugin-catalog-graph@0.4.20-next.0 + - @backstage/plugin-notifications@0.5.6-next.0 + - @backstage/plugin-user-settings@0.8.23-next.0 + - @backstage/plugin-kubernetes@0.12.8-next.0 + - @backstage/plugin-api-docs@0.12.8-next.0 + - @backstage/plugin-devtools@0.1.28-next.0 + - @backstage/plugin-techdocs@1.13.0-next.0 + - @backstage/plugin-catalog@1.31.0-next.0 + - @backstage/plugin-signals@0.0.20-next.0 + - @backstage/plugin-search@1.4.27-next.0 + - @backstage/plugin-home@0.8.9-next.0 + - @backstage/plugin-org@0.6.40-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + - @backstage/frontend-app-api@0.11.3-next.0 + - @backstage/canon@0.5.0-next.0 + - @backstage/plugin-scaffolder-react@1.16.1-next.0 + - @backstage/cli@0.32.1 + - @backstage/plugin-catalog-react@1.18.1-next.0 + - @backstage/plugin-search-react@1.9.1-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.25-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.26-next.0 + - @backstage/integration-react@1.2.7 + ## 0.2.109 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index 604443a0b2..569f84ea7f 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "example-app", - "version": "0.2.109", + "version": "0.2.110-next.1", "backstage": { "role": "frontend" }, diff --git a/packages/app/src/apis.ts b/packages/app/src/apis.ts index e39122337f..5d300d67cb 100644 --- a/packages/app/src/apis.ts +++ b/packages/app/src/apis.ts @@ -24,11 +24,14 @@ import { configApiRef, createApiFactory, discoveryApiRef, + fetchApiRef, } from '@backstage/core-plugin-api'; import { AuthProxyDiscoveryApi } from './AuthProxyDiscoveryApi'; import { formDecoratorsApiRef } from '@backstage/plugin-scaffolder/alpha'; import { DefaultScaffolderFormDecoratorsApi } from '@backstage/plugin-scaffolder/alpha'; import { mockDecorator } from './components/scaffolder/decorators'; +import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react'; +import { ScaffolderClient } from '@backstage/plugin-scaffolder'; export const apis: AnyApiFactory[] = [ createApiFactory({ @@ -42,6 +45,22 @@ export const apis: AnyApiFactory[] = [ factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi), }), + createApiFactory({ + api: scaffolderApiRef, + deps: { + discoveryApi: discoveryApiRef, + fetchApi: fetchApiRef, + scmIntegrationsApi: scmIntegrationsApiRef, + }, + factory: ({ discoveryApi, fetchApi, scmIntegrationsApi }) => + new ScaffolderClient({ + useLongPollingLogs: true, + discoveryApi, + fetchApi, + scmIntegrationsApi, + }), + }), + createApiFactory({ api: formDecoratorsApiRef, deps: {}, diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx index 6f2c85c934..cbf1bcb5f8 100644 --- a/packages/app/src/components/Root/Root.tsx +++ b/packages/app/src/components/Root/Root.tsx @@ -17,14 +17,9 @@ import { PropsWithChildren } from 'react'; import { makeStyles } from '@material-ui/core/styles'; import HomeIcon from '@material-ui/icons/Home'; -import RuleIcon from '@material-ui/icons/AssignmentTurnedIn'; -import MapIcon from '@material-ui/icons/MyLocation'; -import LayersIcon from '@material-ui/icons/Layers'; -import PlaylistPlayIcon from '@material-ui/icons/PlaylistPlay'; import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; import SearchIcon from '@material-ui/icons/Search'; import MenuIcon from '@material-ui/icons/Menu'; -import MoneyIcon from '@material-ui/icons/MonetizationOn'; import LogoFull from './LogoFull'; import LogoIcon from './LogoIcon'; import { @@ -48,10 +43,11 @@ import { } from '@backstage/core-components'; import { MyGroupsSidebarItem } from '@backstage/plugin-org'; import { SearchModal } from '../search/SearchModal'; -import Score from '@material-ui/icons/Score'; import { useApp } from '@backstage/core-plugin-api'; import BuildIcon from '@material-ui/icons/Build'; import { NotificationsSidebarItem } from '@backstage/plugin-notifications'; +import UpdateIcon from '@material-ui/icons/Update'; +import CategoryIcon from '@material-ui/icons/Category'; const useSidebarLogoStyles = makeStyles({ root: { @@ -93,7 +89,8 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( }> {/* Global nav, not org-specific */} - + + ) => ( to="docs" text="Docs" /> - - {/* End global nav */} - - - - - - + + } diff --git a/packages/backend-app-api/CHANGELOG.md b/packages/backend-app-api/CHANGELOG.md index 6c469e9a98..20f035ea76 100644 --- a/packages/backend-app-api/CHANGELOG.md +++ b/packages/backend-app-api/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/backend-app-api +## 1.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 1.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + ## 1.2.3 ### Patch Changes diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index 8e894421f5..b78bb4a5be 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.3", + "version": "1.2.4-next.1", "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 136513ee03..e6237db287 100644 --- a/packages/backend-defaults/CHANGELOG.md +++ b/packages/backend-defaults/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/backend-defaults +## 0.10.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-app-api@1.2.4-next.1 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/cli-node@0.2.13 + - @backstage/config@1.3.2 + - @backstage/config-loader@1.10.1 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/integration-aws-node@0.1.16 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12-next.1 + - @backstage/plugin-permission-node@0.10.1-next.1 + +## 0.10.1-next.0 + +### Patch Changes + +- c999c25: Added some default implementations for the `ActionsService` and `ActionsRegistryService` that allow registration of actions for a particular plugin. +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/backend-app-api@1.2.4-next.0 + - @backstage/cli-node@0.2.13 + - @backstage/config-loader@1.10.1 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + - @backstage/plugin-permission-node@0.10.1-next.0 + ## 0.10.0 ### Minor Changes diff --git a/packages/backend-defaults/config.d.ts b/packages/backend-defaults/config.d.ts index 14732bbccd..1ef054a537 100644 --- a/packages/backend-defaults/config.d.ts +++ b/packages/backend-defaults/config.d.ts @@ -121,6 +121,16 @@ export interface Config { }; }; + /** + * Options used by the default actions service. + */ + actions?: { + /** + * List of plugin sources to load actions from. + */ + pluginSources?: string[]; + }; + /** * Options used by the default auth, httpAuth and userInfo services. */ diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index 600f68d3f4..3c74bad2d5 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-defaults", - "version": "0.10.0", + "version": "0.10.1-next.1", "description": "Backend defaults used by Backstage backend apps", "backstage": { "role": "node-library" @@ -20,6 +20,8 @@ "license": "Apache-2.0", "exports": { ".": "./src/index.ts", + "./actions": "./src/entrypoints/actions/index.ts", + "./actionsRegistry": "./src/entrypoints/actionsRegistry/index.ts", "./auditor": "./src/entrypoints/auditor/index.ts", "./auth": "./src/entrypoints/auth/index.ts", "./cache": "./src/entrypoints/cache/index.ts", @@ -45,6 +47,12 @@ "types": "src/index.ts", "typesVersions": { "*": { + "actions": [ + "src/entrypoints/actions/index.ts" + ], + "actionsRegistry": [ + "src/entrypoints/actionsRegistry/index.ts" + ], "auditor": [ "src/entrypoints/auditor/index.ts" ], @@ -188,7 +196,8 @@ "winston-transport": "^4.5.0", "yauzl": "^3.0.0", "yn": "^4.0.0", - "zod": "^3.22.4" + "zod": "^3.22.4", + "zod-to-json-schema": "^3.20.4" }, "devDependencies": { "@aws-sdk/util-stream-node": "^3.350.0", diff --git a/packages/backend-defaults/report-actions.api.md b/packages/backend-defaults/report-actions.api.md new file mode 100644 index 0000000000..4c072af02a --- /dev/null +++ b/packages/backend-defaults/report-actions.api.md @@ -0,0 +1,17 @@ +## API Report File for "@backstage/backend-defaults" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { ActionsService } from '@backstage/backend-plugin-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; + +// @public (undocumented) +export const actionsServiceFactory: ServiceFactory< + ActionsService, + 'plugin', + 'singleton' +>; + +// (No @packageDocumentation comment for this package) +``` diff --git a/packages/backend-defaults/report-actionsRegistry.api.md b/packages/backend-defaults/report-actionsRegistry.api.md new file mode 100644 index 0000000000..d35bf4cef4 --- /dev/null +++ b/packages/backend-defaults/report-actionsRegistry.api.md @@ -0,0 +1,17 @@ +## API Report File for "@backstage/backend-defaults" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { ActionsRegistryService } from '@backstage/backend-plugin-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; + +// @public (undocumented) +export const actionsRegistryServiceFactory: ServiceFactory< + ActionsRegistryService, + 'plugin', + 'singleton' +>; + +// (No @packageDocumentation comment for this package) +``` diff --git a/packages/backend-defaults/src/CreateBackend.ts b/packages/backend-defaults/src/CreateBackend.ts index d618c94138..032978c806 100644 --- a/packages/backend-defaults/src/CreateBackend.ts +++ b/packages/backend-defaults/src/CreateBackend.ts @@ -35,8 +35,12 @@ import { schedulerServiceFactory } from '@backstage/backend-defaults/scheduler'; import { urlReaderServiceFactory } from '@backstage/backend-defaults/urlReader'; import { userInfoServiceFactory } from '@backstage/backend-defaults/userInfo'; import { eventsServiceFactory } from '@backstage/plugin-events-node'; +import { actionsRegistryServiceFactory } from './entrypoints/actionsRegistry'; +import { actionsServiceFactory } from './entrypoints/actions'; export const defaultServiceFactories = [ + actionsRegistryServiceFactory, + actionsServiceFactory, auditorServiceFactory, authServiceFactory, cacheServiceFactory, diff --git a/packages/backend-defaults/src/entrypoints/actions/DefaultActionsService.ts b/packages/backend-defaults/src/entrypoints/actions/DefaultActionsService.ts new file mode 100644 index 0000000000..2421482f4c --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/actions/DefaultActionsService.ts @@ -0,0 +1,139 @@ +/* + * 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 { + ActionsService, + ActionsServiceAction, + AuthService, + BackstageCredentials, + DiscoveryService, + LoggerService, + RootConfigService, +} from '@backstage/backend-plugin-api'; +import { ResponseError } from '@backstage/errors'; +import { JsonObject } from '@backstage/types'; + +export class DefaultActionsService implements ActionsService { + private constructor( + private readonly discovery: DiscoveryService, + private readonly config: RootConfigService, + private readonly logger: LoggerService, + private readonly auth: AuthService, + ) {} + + static create({ + discovery, + config, + logger, + auth, + }: { + discovery: DiscoveryService; + config: RootConfigService; + logger: LoggerService; + auth: AuthService; + }) { + return new DefaultActionsService(discovery, config, logger, auth); + } + + async list({ credentials }: { credentials: BackstageCredentials }) { + const pluginSources = + this.config.getOptionalStringArray('backend.actions.pluginSources') ?? []; + + const remoteActionsList = await Promise.all( + pluginSources.map(async source => { + try { + const response = await this.makeRequest({ + path: `/.backstage/actions/v1/actions`, + pluginId: source, + credentials, + }); + if (!response.ok) { + throw await ResponseError.fromResponse(response); + } + const { actions } = (await response.json()) as { + actions: ActionsServiceAction; + }; + + return actions; + } catch (error) { + this.logger.warn(`Failed to fetch actions from ${source}`, error); + return []; + } + }), + ); + + return { actions: remoteActionsList.flat() }; + } + + async invoke(opts: { + id: string; + input?: JsonObject; + credentials: BackstageCredentials; + }) { + const pluginId = this.pluginIdFromActionId(opts.id); + const response = await this.makeRequest({ + path: `/.backstage/actions/v1/actions/${encodeURIComponent( + opts.id, + )}/invoke`, + pluginId, + credentials: opts.credentials, + options: { + method: 'POST', + body: JSON.stringify(opts.input), + headers: { + 'Content-Type': 'application/json', + }, + }, + }); + + if (!response.ok) { + throw await ResponseError.fromResponse(response); + } + + const { output } = await response.json(); + return { output }; + } + + private async makeRequest(opts: { + path: string; + pluginId: string; + options?: RequestInit; + credentials: BackstageCredentials; + }) { + const { path, pluginId, credentials, options } = opts; + const baseUrl = await this.discovery.getBaseUrl(pluginId); + + const { token } = await this.auth.getPluginRequestToken({ + onBehalfOf: credentials, + targetPluginId: opts.pluginId, + }); + + return fetch(`${baseUrl}${path}`, { + ...options, + headers: { + ...options?.headers, + Authorization: `Bearer ${token}`, + }, + }); + } + + private pluginIdFromActionId(id: string): string { + const colonIndex = id.indexOf(':'); + if (colonIndex === -1) { + throw new Error(`Invalid action id: ${id}`); + } + return id.substring(0, colonIndex); + } +} diff --git a/packages/backend-defaults/src/entrypoints/actions/actionsServiceFactory.test.ts b/packages/backend-defaults/src/entrypoints/actions/actionsServiceFactory.test.ts new file mode 100644 index 0000000000..7b7285c89a --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/actions/actionsServiceFactory.test.ts @@ -0,0 +1,334 @@ +/* + * 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 { + mockCredentials, + mockServices, + registerMswTestHooks, + ServiceFactoryTester, + startTestBackend, +} from '@backstage/backend-test-utils'; +import { actionsRegistryServiceFactory } from '../actionsRegistry'; +import { httpRouterServiceFactory } from '../httpRouter'; +import { actionsServiceFactory } from './actionsServiceFactory'; +import { setupServer } from 'msw/node'; +import { rest } from 'msw'; +import { + ActionsServiceAction, + coreServices, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; +import { json } from 'express'; +import Router from 'express-promise-router'; +import request from 'supertest'; + +const server = setupServer(); + +describe('actionsServiceFactory', () => { + describe('client', () => { + registerMswTestHooks(server); + const mockActionsListEndpoint = jest.fn(); + const mockNotFoundActionsListEndpoint = jest.fn(); + + const defaultServices = [ + mockServices.rootConfig.factory({ + data: { + backend: { + actions: { + pluginSources: ['my-plugin', 'not-found-plugin'], + }, + }, + }, + }), + actionsServiceFactory, + httpRouterServiceFactory, + mockServices.httpAuth.factory({ + defaultCredentials: mockCredentials.service('user:default/mock'), + }), + mockServices.discovery.factory(), + actionsRegistryServiceFactory, + ]; + + const mockActionsDefinition: ActionsServiceAction = { + description: 'my mock description', + id: 'my-plugin:test', + name: 'testy', + title: 'Test', + schema: { + input: {}, + output: {}, + }, + }; + + beforeEach(() => { + server.use( + rest.get( + 'http://localhost:0/api/my-plugin/.backstage/actions/v1/actions', + mockActionsListEndpoint.mockImplementation((_req, res, ctx) => + res( + ctx.json({ + actions: [mockActionsDefinition], + }), + ), + ), + ), + rest.get( + 'http://localhost:0/api/not-found-plugin/.backstage/actions/v1/actions', + mockNotFoundActionsListEndpoint.mockImplementation((_req, res, ctx) => + res(ctx.status(404)), + ), + ), + ); + }); + + describe('list', () => { + it('should list all plugins in config to find actions and handle failures gracefully', async () => { + const subject = await ServiceFactoryTester.from(actionsServiceFactory, { + dependencies: defaultServices, + }).getSubject(); + + const { actions } = await subject.list({ + credentials: mockCredentials.service('user:default/mock'), + }); + + expect(actions).toEqual([mockActionsDefinition]); + + expect(mockActionsListEndpoint).toHaveBeenCalledTimes(1); + expect(mockNotFoundActionsListEndpoint).toHaveBeenCalledTimes(1); + }); + }); + + describe('invoke', () => { + it('should invoke the action and return the output', async () => { + server.use( + rest.post( + 'http://localhost:0/api/my-plugin/.backstage/actions/v1/actions/my-plugin:test/invoke', + (_req, res, ctx) => res(ctx.json({ output: { ok: true } })), + ), + ); + const subject = await ServiceFactoryTester.from(actionsServiceFactory, { + dependencies: defaultServices, + }).getSubject(); + + const { output } = await subject.invoke({ + id: 'my-plugin:test', + credentials: mockCredentials.service('user:default/mock'), + }); + + expect(output).toEqual({ ok: true }); + }); + + it('should throw a 404 if the action does not exist', async () => { + server.use( + rest.post( + 'http://localhost:0/api/my-plugin/.backstage/actions/v1/actions/my-plugin:test/invoke', + (_req, res, ctx) => res(ctx.status(404)), + ), + ); + + const subject = await ServiceFactoryTester.from(actionsServiceFactory, { + dependencies: defaultServices, + }).getSubject(); + + await expect( + subject.invoke({ + id: 'my-plugin:test', + credentials: mockCredentials.service('user:default/mock'), + }), + ).rejects.toThrow('404'); + }); + + it('should throw a 400 if the action returns an invalid input', async () => { + server.use( + rest.post( + 'http://localhost:0/api/my-plugin/.backstage/actions/v1/actions/my-plugin:test/invoke', + (_req, res, ctx) => res(ctx.status(400)), + ), + ); + + const subject = await ServiceFactoryTester.from(actionsServiceFactory, { + dependencies: defaultServices, + }).getSubject(); + + await expect( + subject.invoke({ + id: 'my-plugin:test', + credentials: mockCredentials.service('user:default/mock'), + }), + ).rejects.toThrow('400'); + }); + }); + + describe('integration', () => { + beforeAll(() => { + // disable the msw server for this test. + server.close(); + }); + + const features = [ + actionsServiceFactory, + httpRouterServiceFactory, + mockServices.httpAuth.factory({ + defaultCredentials: mockCredentials.service('user:default/mock'), + }), + actionsRegistryServiceFactory, + mockServices.rootConfig.factory({ + data: { + backend: { + actions: { pluginSources: ['plugin-with-action'] }, + }, + }, + }), + + createBackendPlugin({ + pluginId: 'plugin-with-action', + register({ registerInit }) { + registerInit({ + deps: { actionsRegistry: coreServices.actionsRegistry }, + async init({ actionsRegistry }) { + actionsRegistry.register({ + name: 'with-validation', + title: 'Test', + description: 'Test', + schema: { + input: z => + z.object({ + name: z.string(), + }), + output: z => + z.object({ + ok: z.boolean(), + string: z.string(), + }), + }, + action: async ({ input }) => { + return { + output: { + ok: true, + string: `hello ${input.name}`, + }, + }; + }, + }); + }, + }); + }, + }), + createBackendPlugin({ + pluginId: 'test-harness', + register({ registerInit }) { + registerInit({ + deps: { + actionsService: coreServices.actions, + router: coreServices.httpRouter, + httpAuth: coreServices.httpAuth, + }, + async init({ actionsService, router, httpAuth }) { + const innerRouter = Router(); + innerRouter.use(json()); + router.use(innerRouter); + + innerRouter.post('/invoke', async (req, res) => { + const { id, input } = req.body; + const response = await actionsService.invoke({ + id, + input, + credentials: await httpAuth.credentials(req), + }); + res.json(response); + }); + + innerRouter.get('/actions', async (req, res) => { + const response = await actionsService.list({ + credentials: await httpAuth.credentials(req), + }); + + res.json(response); + }); + }, + }); + }, + }), + ]; + + it('should list the actions and return the output', async () => { + const { server: testHarnessServer } = await startTestBackend({ + features, + }); + + const { body, status } = await request(testHarnessServer).get( + '/api/test-harness/actions', + ); + + expect(status).toBe(200); + expect(body).toEqual({ + actions: [ + { + description: 'Test', + id: 'plugin-with-action:with-validation', + name: 'with-validation', + schema: { + input: { + $schema: 'http://json-schema.org/draft-07/schema#', + additionalProperties: false, + properties: { + name: { + type: 'string', + }, + }, + required: ['name'], + type: 'object', + }, + output: { + $schema: 'http://json-schema.org/draft-07/schema#', + additionalProperties: false, + properties: { + ok: { + type: 'boolean', + }, + string: { + type: 'string', + }, + }, + required: ['ok', 'string'], + type: 'object', + }, + }, + title: 'Test', + }, + ], + }); + }); + + it('should invoke the action and return the output', async () => { + const { server: testHarnessServer } = await startTestBackend({ + features, + }); + + const { body, status } = await request(testHarnessServer) + .post('/api/test-harness/invoke') + .send({ + id: 'plugin-with-action:with-validation', + input: { + name: 'world', + }, + }); + + expect(body).toEqual({ output: { ok: true, string: 'hello world' } }); + expect(status).toBe(200); + }); + }); + }); +}); diff --git a/packages/backend-defaults/src/entrypoints/actions/actionsServiceFactory.ts b/packages/backend-defaults/src/entrypoints/actions/actionsServiceFactory.ts new file mode 100644 index 0000000000..466cc02121 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/actions/actionsServiceFactory.ts @@ -0,0 +1,38 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createServiceFactory } from '@backstage/backend-plugin-api'; +import { coreServices } from '@backstage/backend-plugin-api'; +import { DefaultActionsService } from './DefaultActionsService'; + +/** + * @public + */ +export const actionsServiceFactory = createServiceFactory({ + service: coreServices.actions, + deps: { + discovery: coreServices.discovery, + config: coreServices.rootConfig, + logger: coreServices.logger, + auth: coreServices.auth, + }, + factory: ({ discovery, config, logger, auth }) => + DefaultActionsService.create({ + discovery, + config, + logger, + auth, + }), +}); diff --git a/plugins/scaffolder-backend/src/service/index.ts b/packages/backend-defaults/src/entrypoints/actions/index.ts similarity index 84% rename from plugins/scaffolder-backend/src/service/index.ts rename to packages/backend-defaults/src/entrypoints/actions/index.ts index dc5508d47f..bc34150a5f 100644 --- a/plugins/scaffolder-backend/src/service/index.ts +++ b/packages/backend-defaults/src/entrypoints/actions/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,5 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -export * from './conditionExports'; +export { actionsServiceFactory } from './actionsServiceFactory'; diff --git a/packages/backend-defaults/src/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts b/packages/backend-defaults/src/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts new file mode 100644 index 0000000000..17523a2ee5 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts @@ -0,0 +1,156 @@ +/* + * 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 { + ActionsRegistryActionOptions, + ActionsRegistryService, + AuthService, + HttpAuthService, + LoggerService, + PluginMetadataService, +} from '@backstage/backend-plugin-api'; +import PromiseRouter from 'express-promise-router'; +import { Router, json } from 'express'; +import { z, ZodType } from 'zod'; +import zodToJsonSchema from 'zod-to-json-schema'; +import { + ForwardedError, + InputError, + NotAllowedError, + NotFoundError, +} from '@backstage/errors'; + +export class DefaultActionsRegistryService implements ActionsRegistryService { + private actions: Map> = + new Map(); + + private constructor( + private readonly logger: LoggerService, + private readonly httpAuth: HttpAuthService, + private readonly auth: AuthService, + private readonly metadata: PluginMetadataService, + ) {} + + static create({ + httpAuth, + logger, + auth, + metadata, + }: { + httpAuth: HttpAuthService; + logger: LoggerService; + auth: AuthService; + metadata: PluginMetadataService; + }): DefaultActionsRegistryService { + return new DefaultActionsRegistryService(logger, httpAuth, auth, metadata); + } + + createRouter(): Router { + const router = PromiseRouter(); + router.use(json()); + + router.get('/.backstage/actions/v1/actions', (_, res) => { + return res.json({ + actions: Array.from(this.actions.entries()).map(([id, action]) => ({ + id, + ...action, + schema: { + input: action.schema?.input + ? zodToJsonSchema(action.schema.input(z)) + : zodToJsonSchema(z.any()), + output: action.schema?.output + ? zodToJsonSchema(action.schema.output(z)) + : zodToJsonSchema(z.any()), + }, + })), + }); + }); + + router.post( + '/.backstage/actions/v1/actions/:actionId/invoke', + async (req, res) => { + const credentials = await this.httpAuth.credentials(req); + if (this.auth.isPrincipal(credentials, 'user')) { + if (!credentials.principal.actor) { + throw new NotAllowedError( + `Actions must be invoked by a service, not a user`, + ); + } + } else if (this.auth.isPrincipal(credentials, 'none')) { + throw new NotAllowedError( + `Actions must be invoked by a service, not an anonymous request`, + ); + } + + const action = this.actions.get(req.params.actionId); + + if (!action) { + throw new NotFoundError(`Action "${req.params.actionId}" not found`); + } + + const input = action.schema?.input + ? action.schema.input(z).safeParse(req.body) + : ({ success: true, data: undefined } as const); + + if (!input.success) { + throw new InputError( + `Invalid input to action "${req.params.actionId}"`, + input.error, + ); + } + + try { + const result = await action.action({ + input: input.data, + credentials, + logger: this.logger, + }); + + const output = action.schema?.output + ? action.schema.output(z).safeParse(result?.output) + : ({ success: true, data: result?.output } as const); + + if (!output.success) { + throw new InputError( + `Invalid output from action "${req.params.actionId}"`, + output.error, + ); + } + + res.json({ output: output.data }); + } catch (error) { + throw new ForwardedError( + `Failed execution of action "${req.params.actionId}"`, + error, + ); + } + }, + ); + return router; + } + + register( + options: ActionsRegistryActionOptions, + ): void { + const id = `${this.metadata.getId()}:${options.name}`; + + if (this.actions.has(id)) { + throw new Error(`Action with id "${id}" is already registered`); + } + + this.actions.set(id, options); + } +} diff --git a/packages/backend-defaults/src/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts b/packages/backend-defaults/src/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts new file mode 100644 index 0000000000..5d47ff055d --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts @@ -0,0 +1,434 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + coreServices, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; +import { + mockCredentials, + mockServices, + startTestBackend, +} from '@backstage/backend-test-utils'; +import { httpRouterServiceFactory } from '../httpRouter'; +import request from 'supertest'; +import { actionsRegistryServiceFactory } from './actionsRegistryServiceFactory'; +import { InputError } from '@backstage/errors'; + +describe('actionsRegistryServiceFactory', () => { + const defaultServices = [ + actionsRegistryServiceFactory, + httpRouterServiceFactory, + mockServices.httpAuth.factory({ + defaultCredentials: mockCredentials.service('user:default/mock'), + }), + ]; + + describe('typescript tests', () => { + it('should properly infer the input types', () => { + createBackendPlugin({ + pluginId: 'my-plugin', + register(reg) { + reg.registerInit({ + deps: { + actionsRegistry: coreServices.actionsRegistry, + }, + async init({ actionsRegistry }) { + actionsRegistry.register({ + name: 'test', + title: 'Test', + description: 'Test', + schema: { + input: z => + z.object({ + test: z.string(), + }), + output: z => + z.object({ + ok: z.boolean(), + }), + }, + action: async ({ input: { test } }) => { + // @ts-expect-error - test is not a boolean + const _t: boolean = test; + return { output: { ok: true } }; + }, + }); + }, + }); + }, + }); + + expect(true).toBe(true); + }); + + it('should properly infer the output types', () => { + createBackendPlugin({ + pluginId: 'my-plugin', + register(reg) { + reg.registerInit({ + deps: { + actionsRegistry: coreServices.actionsRegistry, + }, + async init({ actionsRegistry }) { + actionsRegistry.register({ + name: 'test', + title: 'Test', + description: 'Test', + schema: { + input: z => + z.object({ + test: z.string(), + }), + output: z => + z.object({ + ok: z.boolean(), + }), + }, + // @ts-expect-error - ok is not a boolean + action: async () => { + return { output: { ok: 'bo' } }; + }, + }); + }, + }); + }, + }); + + expect(true).toBe(true); + }); + }); + + describe('/.backstage/actions/v1/actions', () => { + it('should allow registering of actions', async () => { + const pluginSubject = createBackendPlugin({ + pluginId: 'my-plugin', + register(reg) { + reg.registerInit({ + deps: { + actionsRegistry: coreServices.actionsRegistry, + }, + async init({ actionsRegistry }) { + actionsRegistry.register({ + name: 'test', + title: 'Test', + description: 'Test', + schema: { + input: z => + z.object({ + name: z.string(), + }), + output: z => + z.object({ + ok: z.boolean(), + }), + }, + action: async () => ({ output: { ok: true } }), + }); + }, + }); + }, + }); + + const { server } = await startTestBackend({ + features: [pluginSubject, ...defaultServices], + }); + + const { body, status } = await request(server).get( + '/api/my-plugin/.backstage/actions/v1/actions', + ); + + expect(status).toBe(200); + + expect(body).toMatchObject({ + actions: [ + { + name: 'test', + title: 'Test', + description: 'Test', + schema: { + input: { + type: 'object', + properties: { + name: { + type: 'string', + }, + }, + }, + output: { + type: 'object', + properties: { + ok: { + type: 'boolean', + }, + }, + }, + }, + }, + ], + }); + }); + + it('should allow registering of actions with no schema', async () => { + const pluginSubject = createBackendPlugin({ + pluginId: 'my-plugin', + register(reg) { + reg.registerInit({ + deps: { + actionsRegistry: coreServices.actionsRegistry, + }, + async init({ actionsRegistry }) { + actionsRegistry.register({ + name: 'test', + title: 'Test', + description: 'Test', + schema: { + input: z => z.undefined(), + output: z => z.string(), + }, + action: async () => ({ output: 'ok' }), + }); + }, + }); + }, + }); + + const { server } = await startTestBackend({ + features: [pluginSubject, ...defaultServices], + }); + + const { body, status } = await request(server).get( + '/api/my-plugin/.backstage/actions/v1/actions', + ); + + expect(status).toBe(200); + + expect(body).toMatchObject({ + actions: [ + { + name: 'test', + title: 'Test', + description: 'Test', + schema: { + input: {}, + output: {}, + }, + }, + ], + }); + }); + }); + + describe('/.backstage/actions/v1/actions/:actionId/invoke', () => { + const mockAction = jest.fn(); + + beforeEach(() => { + mockAction.mockResolvedValue({ output: { ok: true } }); + }); + + const pluginSubject = createBackendPlugin({ + pluginId: 'my-plugin', + register(reg) { + reg.registerInit({ + deps: { + actionsRegistry: coreServices.actionsRegistry, + }, + async init({ actionsRegistry }) { + actionsRegistry.register({ + name: 'test', + title: 'Test', + description: 'Test', + schema: { + input: z => + z.object({ + name: z.string(), + }), + output: z => + z.object({ + ok: z.boolean(), + }), + }, + action: mockAction, + }); + }, + }); + }, + }); + + it('should throw an error if the action is not found', async () => { + const { server } = await startTestBackend({ + features: [pluginSubject, ...defaultServices], + }); + + const { body, status } = await request(server).post( + '/api/my-plugin/.backstage/actions/v1/actions/test/invoke', + ); + + expect(status).toBe(404); + expect(body).toMatchObject({ + error: { + message: 'Action "test" not found', + }, + }); + }); + + it('should throw an error if the action input does not match the schema', async () => { + const { server } = await startTestBackend({ + features: [pluginSubject, ...defaultServices], + }); + + const { body, status } = await request(server) + .post( + '/api/my-plugin/.backstage/actions/v1/actions/my-plugin:test/invoke', + ) + .send({ + name: 123, + }); + + expect(status).toBe(400); + expect(body).toMatchObject({ + error: { + message: expect.stringMatching( + 'Invalid input to action "my-plugin:test"', + ), + }, + }); + }); + + it('should call the action with the input', async () => { + const { server } = await startTestBackend({ + features: [pluginSubject, ...defaultServices], + }); + + await request(server) + .post( + '/api/my-plugin/.backstage/actions/v1/actions/my-plugin:test/invoke', + ) + .send({ + name: 'test', + }); + + expect(mockAction).toHaveBeenCalledWith({ + input: { + name: 'test', + }, + credentials: { + $$type: '@backstage/BackstageCredentials', + principal: { + type: 'service', + subject: 'user:default/mock', + }, + }, + logger: expect.anything(), + }); + }); + + it('should throw an error if the action is invoked by a user', async () => { + const testServices = [ + actionsRegistryServiceFactory, + httpRouterServiceFactory, + mockServices.httpAuth.factory({ + defaultCredentials: mockCredentials.user(), + }), + ]; + + const { server } = await startTestBackend({ + features: [pluginSubject, ...testServices], + }); + + const { body, status } = await request(server) + .post( + '/api/my-plugin/.backstage/actions/v1/actions/my-plugin:test/invoke', + ) + .send({ + name: 'test', + }); + + expect(status).toBe(403); + expect(body).toMatchObject({ + error: { + message: 'Actions must be invoked by a service, not a user', + }, + }); + }); + + it('should validate the output of the action if provided', async () => { + const { server } = await startTestBackend({ + features: [pluginSubject, ...defaultServices], + }); + + mockAction.mockResolvedValue({ ok: 'blob' }); + + const { body, status } = await request(server) + .post( + '/api/my-plugin/.backstage/actions/v1/actions/my-plugin:test/invoke', + ) + .send({ + name: 'test', + }); + + expect(status).toBe(400); + expect(body).toMatchObject({ + error: { + message: expect.stringMatching( + 'Invalid output from action "my-plugin:test"', + ), + }, + }); + }); + + it('should return the output of the action', async () => { + const { server } = await startTestBackend({ + features: [pluginSubject, ...defaultServices], + }); + + const { body, status } = await request(server) + .post( + '/api/my-plugin/.backstage/actions/v1/actions/my-plugin:test/invoke', + ) + .send({ + name: 'test', + }); + + expect(status).toBe(200); + expect(body).toMatchObject({ output: { ok: true } }); + }); + + it('should return the error from the action if it throws', async () => { + const { server } = await startTestBackend({ + features: [pluginSubject, ...defaultServices], + }); + + mockAction.mockRejectedValue(new InputError('test')); + + const { body, status } = await request(server) + .post( + '/api/my-plugin/.backstage/actions/v1/actions/my-plugin:test/invoke', + ) + .send({ + name: 'test', + }); + + expect(status).toBe(400); + expect(body).toMatchObject({ + error: { + message: expect.stringContaining( + 'Failed execution of action "my-plugin:test"', + ), + }, + }); + }); + }); +}); diff --git a/packages/backend-defaults/src/entrypoints/actionsRegistry/actionsRegistryServiceFactory.ts b/packages/backend-defaults/src/entrypoints/actionsRegistry/actionsRegistryServiceFactory.ts new file mode 100644 index 0000000000..85515a6046 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/actionsRegistry/actionsRegistryServiceFactory.ts @@ -0,0 +1,47 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + coreServices, + createServiceFactory, +} from '@backstage/backend-plugin-api'; +import { DefaultActionsRegistryService } from './DefaultActionsRegistryService'; + +/** + * @public + */ +export const actionsRegistryServiceFactory = createServiceFactory({ + service: coreServices.actionsRegistry, + deps: { + metadata: coreServices.pluginMetadata, + httpRouter: coreServices.httpRouter, + httpAuth: coreServices.httpAuth, + logger: coreServices.logger, + auth: coreServices.auth, + }, + factory: ({ metadata, httpRouter, httpAuth, logger, auth }) => { + const actionsRegistryService = DefaultActionsRegistryService.create({ + httpAuth, + logger, + auth, + metadata, + }); + + httpRouter.use(actionsRegistryService.createRouter()); + + return actionsRegistryService; + }, +}); diff --git a/plugins/scaffolder-backend/src/lib/templating/index.ts b/packages/backend-defaults/src/entrypoints/actionsRegistry/index.ts similarity index 82% rename from plugins/scaffolder-backend/src/lib/templating/index.ts rename to packages/backend-defaults/src/entrypoints/actionsRegistry/index.ts index 1f9e5eb855..ac65dd7a90 100644 --- a/plugins/scaffolder-backend/src/lib/templating/index.ts +++ b/packages/backend-defaults/src/entrypoints/actionsRegistry/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2022 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,5 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -export type { TemplateFilter, TemplateGlobal } from './SecureTemplater'; +export { actionsRegistryServiceFactory } from './actionsRegistryServiceFactory'; diff --git a/packages/backend-dynamic-feature-service/CHANGELOG.md b/packages/backend-dynamic-feature-service/CHANGELOG.md index 6620d59216..390629ace4 100644 --- a/packages/backend-dynamic-feature-service/CHANGELOG.md +++ b/packages/backend-dynamic-feature-service/CHANGELOG.md @@ -1,5 +1,49 @@ # @backstage/backend-dynamic-feature-service +## 0.7.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/plugin-catalog-backend@2.0.1-next.1 + - @backstage/backend-defaults@0.10.1-next.1 + - @backstage/backend-openapi-utils@0.5.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/cli-common@0.1.15 + - @backstage/cli-node@0.2.13 + - @backstage/config@1.3.2 + - @backstage/config-loader@1.10.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-app-node@0.1.34-next.1 + - @backstage/plugin-events-backend@0.5.3-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1-next.1 + - @backstage/plugin-search-backend-node@1.3.12-next.1 + - @backstage/plugin-search-common@1.2.18 + +## 0.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/plugin-catalog-backend@2.0.1-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/backend-defaults@0.10.1-next.0 + - @backstage/plugin-events-backend@0.5.3-next.0 + - @backstage/cli-node@0.2.13 + - @backstage/config-loader@1.10.1 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + - @backstage/plugin-permission-node@0.10.1-next.0 + - @backstage/plugin-search-backend-node@1.3.12-next.0 + - @backstage/backend-openapi-utils@0.5.4-next.0 + - @backstage/plugin-app-node@0.1.34-next.0 + ## 0.7.0 ### Minor Changes diff --git a/packages/backend-dynamic-feature-service/package.json b/packages/backend-dynamic-feature-service/package.json index 78876d99d9..bf2a88e015 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.0", + "version": "0.7.1-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 5e070f5e8d..bfdc32df2e 100644 --- a/packages/backend-openapi-utils/CHANGELOG.md +++ b/packages/backend-openapi-utils/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/backend-openapi-utils +## 0.5.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.5.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + ## 0.5.3 ### Patch Changes diff --git a/packages/backend-openapi-utils/package.json b/packages/backend-openapi-utils/package.json index 97be1762da..d4b17a4e61 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.5.3", + "version": "0.5.4-next.1", "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 6edd3e2c78..3aea27d1de 100644 --- a/packages/backend-plugin-api/CHANGELOG.md +++ b/packages/backend-plugin-api/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/backend-plugin-api +## 1.4.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1-next.1 + +## 1.4.0-next.0 + +### Minor Changes + +- 664c07a: Added `coreServices.actionsRegistry` and `coreServices.actions` to allow registration of distributed actions from plugins, and the ability to invoke these actions + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-permission-node@0.10.1-next.0 + ## 1.3.1 ### Patch Changes diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index 17ff606187..52c1c6a66a 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.3.1", + "version": "1.4.0-next.1", "description": "Core API used by Backstage backend plugins", "backstage": { "role": "node-library" @@ -61,9 +61,12 @@ "@backstage/plugin-permission-node": "workspace:^", "@backstage/types": "workspace:^", "@types/express": "^4.17.6", + "@types/json-schema": "^7.0.6", "@types/luxon": "^3.0.0", + "json-schema": "^0.4.0", "knex": "^3.0.0", - "luxon": "^3.0.0" + "luxon": "^3.0.0", + "zod": "^3.22.4" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/packages/backend-plugin-api/report.api.md b/packages/backend-plugin-api/report.api.md index 52843dec96..68597dcbe1 100644 --- a/packages/backend-plugin-api/report.api.md +++ b/packages/backend-plugin-api/report.api.md @@ -12,6 +12,7 @@ import type { Handler } from 'express'; import { HumanDuration } from '@backstage/types'; import { isChildPath } from '@backstage/cli-common'; import { JsonObject } from '@backstage/types'; +import { JSONSchema7 } from 'json-schema'; import { JsonValue } from '@backstage/types'; import { Knex } from 'knex'; import { Permission } from '@backstage/plugin-permission-common'; @@ -25,6 +26,72 @@ import { QueryPermissionResponse } from '@backstage/plugin-permission-common'; import { Readable } from 'stream'; import type { Request as Request_2 } from 'express'; import type { Response as Response_2 } from 'express'; +import { z } from 'zod'; +import { ZodType } from 'zod'; + +// @public (undocumented) +export type ActionsRegistryActionContext = { + input: z.infer; + logger: LoggerService; + credentials: BackstageCredentials; +}; + +// @public (undocumented) +export type ActionsRegistryActionOptions< + TInputSchema extends ZodType, + TOutputSchema extends ZodType, +> = { + name: string; + title: string; + description: string; + schema: { + input: (zod: typeof z) => TInputSchema; + output: (zod: typeof z) => TOutputSchema; + }; + action: (context: ActionsRegistryActionContext) => Promise< + z.infer extends void + ? void + : { + output: z.infer; + } + >; +}; + +// @public (undocumented) +export interface ActionsRegistryService { + // (undocumented) + register( + options: ActionsRegistryActionOptions, + ): void; +} + +// @public (undocumented) +export interface ActionsService { + // (undocumented) + invoke(opts: { + id: string; + input?: JsonObject; + credentials: BackstageCredentials; + }): Promise<{ + output: JsonValue; + }>; + // (undocumented) + list: (opts: { credentials: BackstageCredentials }) => Promise<{ + actions: ActionsServiceAction[]; + }>; +} + +// @public (undocumented) +export type ActionsServiceAction = { + id: string; + name: string; + title: string; + description: string; + schema: { + input: JSONSchema7; + output: JSONSchema7; + }; +}; // @public export interface AuditorService { @@ -205,6 +272,12 @@ export type CacheServiceSetOptions = { // @public export namespace coreServices { const auth: ServiceRef; + const actions: ServiceRef; + const actionsRegistry: ServiceRef< + ActionsRegistryService, + 'plugin', + 'singleton' + >; const userInfo: ServiceRef; const cache: ServiceRef; const rootConfig: ServiceRef; diff --git a/packages/backend-plugin-api/src/services/definitions/ActionsRegistryService.ts b/packages/backend-plugin-api/src/services/definitions/ActionsRegistryService.ts new file mode 100644 index 0000000000..3044c84ed8 --- /dev/null +++ b/packages/backend-plugin-api/src/services/definitions/ActionsRegistryService.ts @@ -0,0 +1,59 @@ +/* + * 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 { z, ZodType } from 'zod'; +import { LoggerService } from './LoggerService'; +import { BackstageCredentials } from './AuthService'; + +/** + * @public + */ +export type ActionsRegistryActionContext = { + input: z.infer; + logger: LoggerService; + credentials: BackstageCredentials; +}; + +/** + * @public + */ +export type ActionsRegistryActionOptions< + TInputSchema extends ZodType, + TOutputSchema extends ZodType, +> = { + name: string; + title: string; + description: string; + schema: { + input: (zod: typeof z) => TInputSchema; + output: (zod: typeof z) => TOutputSchema; + }; + action: ( + context: ActionsRegistryActionContext, + ) => Promise< + z.infer extends void + ? void + : { output: z.infer } + >; +}; + +/** + * @public + */ +export interface ActionsRegistryService { + register( + options: ActionsRegistryActionOptions, + ): void; +} diff --git a/packages/backend-plugin-api/src/services/definitions/ActionsService.ts b/packages/backend-plugin-api/src/services/definitions/ActionsService.ts new file mode 100644 index 0000000000..69b13afaaa --- /dev/null +++ b/packages/backend-plugin-api/src/services/definitions/ActionsService.ts @@ -0,0 +1,46 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { JsonObject, JsonValue } from '@backstage/types'; +import { JSONSchema7 } from 'json-schema'; +import { BackstageCredentials } from './AuthService'; + +/** + * @public + */ +export type ActionsServiceAction = { + id: string; + name: string; + title: string; + description: string; + schema: { + input: JSONSchema7; + output: JSONSchema7; + }; +}; + +/** + * @public + */ +export interface ActionsService { + list: (opts: { + credentials: BackstageCredentials; + }) => Promise<{ actions: ActionsServiceAction[] }>; + invoke(opts: { + id: string; + input?: JsonObject; + credentials: BackstageCredentials; + }): Promise<{ output: JsonValue }>; +} diff --git a/packages/backend-plugin-api/src/services/definitions/coreServices.ts b/packages/backend-plugin-api/src/services/definitions/coreServices.ts index 8c8c6c0f82..c5282368ac 100644 --- a/packages/backend-plugin-api/src/services/definitions/coreServices.ts +++ b/packages/backend-plugin-api/src/services/definitions/coreServices.ts @@ -35,6 +35,36 @@ export namespace coreServices { id: 'core.auth', }); + /** + * Service for calling distributed actions + * + * See {@link ActionsService} + * and {@link https://backstage.io/docs/backend-system/core-services/actions | the service docs} + * for more information. + * + * @public + */ + export const actions = createServiceRef< + import('./ActionsService').ActionsService + >({ + id: 'core.actions', + }); + + /** + * Service for registering and managing distributed actions. + * + * See {@link ActionsRegistryService} + * and {@link https://backstage.io/docs/backend-system/core-services/actions-registry | the service docs} + * for more information. + * + * @public + */ + export const actionsRegistry = createServiceRef< + import('./ActionsRegistryService').ActionsRegistryService + >({ + id: 'core.actionsRegistry', + }); + /** * Authenticated user information retrieval. * diff --git a/packages/backend-plugin-api/src/services/definitions/index.ts b/packages/backend-plugin-api/src/services/definitions/index.ts index d6346cbe4e..a01544b60f 100644 --- a/packages/backend-plugin-api/src/services/definitions/index.ts +++ b/packages/backend-plugin-api/src/services/definitions/index.ts @@ -14,6 +14,13 @@ * limitations under the License. */ +export type { + ActionsRegistryService, + ActionsRegistryActionOptions, + ActionsRegistryActionContext, +} from './ActionsRegistryService'; + +export type { ActionsService, ActionsServiceAction } from './ActionsService'; export type { AuditorService, AuditorServiceCreateEventOptions, diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md index 377e15237b..58c728d457 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/backend-test-utils +## 1.6.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-app-api@1.2.4-next.1 + - @backstage/backend-defaults@0.10.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 1.6.0-next.0 + +### Minor Changes + +- c999c25: Added mock implementations for `ActionsService` and `ActionsRegistryService` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/backend-defaults@0.10.1-next.0 + - @backstage/backend-app-api@1.2.4-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + ## 1.5.0 ### Minor Changes diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index e64938714f..26ba29ad0f 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.5.0", + "version": "1.6.0-next.1", "description": "Test helpers library for Backstage backends", "backstage": { "role": "node-library" diff --git a/packages/backend-test-utils/report.api.md b/packages/backend-test-utils/report.api.md index 7edcf6e1b7..379a2a70ac 100644 --- a/packages/backend-test-utils/report.api.md +++ b/packages/backend-test-utils/report.api.md @@ -3,6 +3,8 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { ActionsRegistryService } from '@backstage/backend-plugin-api'; +import { ActionsService } from '@backstage/backend-plugin-api'; import { AuditorService } from '@backstage/backend-plugin-api'; import { AuthService } from '@backstage/backend-plugin-api'; import { Backend } from '@backstage/backend-app-api'; @@ -161,6 +163,28 @@ export function mockErrorHandler(): ErrorRequestHandler< // @public export namespace mockServices { + // (undocumented) + export namespace actions { + const // (undocumented) + factory: () => ServiceFactory; + const // (undocumented) + mock: ( + partialImpl?: Partial | undefined, + ) => ServiceMock; + } + // (undocumented) + export namespace actionsRegistry { + const // (undocumented) + factory: () => ServiceFactory< + ActionsRegistryService, + 'plugin', + 'singleton' + >; + const // (undocumented) + mock: ( + partialImpl?: Partial | undefined, + ) => ServiceMock; + } // (undocumented) export namespace auditor { const // (undocumented) diff --git a/packages/backend-test-utils/src/next/services/mockServices.ts b/packages/backend-test-utils/src/next/services/mockServices.ts index f68f1337b9..0c5b18bd1e 100644 --- a/packages/backend-test-utils/src/next/services/mockServices.ts +++ b/packages/backend-test-utils/src/next/services/mockServices.ts @@ -53,6 +53,8 @@ import { MockRootLoggerService } from './MockRootLoggerService'; import { MockUserInfoService } from './MockUserInfoService'; import { mockCredentials } from './mockCredentials'; import { MockEventsService } from './MockEventsService'; +import { actionsServiceFactory } from '@backstage/backend-defaults/actions'; +import { actionsRegistryServiceFactory } from '@backstage/backend-defaults/actionsRegistry'; /** @internal */ function createLoggerMock() { @@ -518,6 +520,20 @@ export namespace mockServices { search: jest.fn(), })); } + export namespace actions { + export const factory = () => actionsServiceFactory; + export const mock = simpleMock(coreServices.actions, () => ({ + list: jest.fn(), + invoke: jest.fn(), + })); + } + + export namespace actionsRegistry { + export const factory = () => actionsRegistryServiceFactory; + export const mock = simpleMock(coreServices.actionsRegistry, () => ({ + register: jest.fn(), + })); + } /** * Creates a functional mock implementation of the diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index a197dd647f..9fd8ca9920 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,81 @@ # example-backend +## 0.0.39-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.11-next.1 + - @backstage/plugin-notifications-backend@0.5.7-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.4.3-next.1 + - @backstage/plugin-scaffolder-backend@2.0.0-next.1 + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/plugin-catalog-backend@2.0.1-next.1 + - @backstage/plugin-kubernetes-backend@0.19.7-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.7.2-next.1 + - @backstage/plugin-search-backend-module-catalog@0.3.5-next.1 + - @backstage/plugin-techdocs-backend@2.0.3-next.1 + - @backstage/backend-defaults@0.10.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-app-backend@0.5.3-next.1 + - @backstage/plugin-auth-backend@0.25.1-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.3.4-next.1 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.9-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.3-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.2.11-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.9-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.1-next.1 + - @backstage/plugin-devtools-backend@0.5.6-next.1 + - @backstage/plugin-events-backend@0.5.3-next.1 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.1-next.1 + - @backstage/plugin-permission-backend@0.7.1-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.9-next.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1-next.1 + - @backstage/plugin-proxy-backend@0.6.3-next.1 + - @backstage/plugin-search-backend@2.0.3-next.1 + - @backstage/plugin-search-backend-module-explore@0.3.3-next.1 + - @backstage/plugin-search-backend-node@1.3.12-next.1 + - @backstage/plugin-signals-backend@0.3.5-next.1 + +## 0.0.39-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@2.0.1-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/backend-defaults@0.10.1-next.0 + - @backstage/plugin-events-backend@0.5.3-next.0 + - @backstage/plugin-search-backend@2.0.3-next.0 + - @backstage/plugin-scaffolder-backend@1.33.1-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.7.2-next.0 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.11-next.0 + - @backstage/plugin-techdocs-backend@2.0.3-next.0 + - @backstage/plugin-app-backend@0.5.3-next.0 + - @backstage/plugin-auth-backend@0.25.1-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.3.4-next.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.9-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.2.11-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.9-next.0 + - @backstage/plugin-devtools-backend@0.5.6-next.0 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.1-next.0 + - @backstage/plugin-kubernetes-backend@0.19.7-next.0 + - @backstage/plugin-notifications-backend@0.5.7-next.0 + - @backstage/plugin-permission-backend@0.7.1-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.9-next.0 + - @backstage/plugin-permission-node@0.10.1-next.0 + - @backstage/plugin-proxy-backend@0.6.3-next.0 + - @backstage/plugin-search-backend-module-catalog@0.3.5-next.0 + - @backstage/plugin-search-backend-module-explore@0.3.3-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.4.3-next.0 + - @backstage/plugin-search-backend-node@1.3.12-next.0 + - @backstage/plugin-signals-backend@0.3.5-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.3-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.1-next.0 + ## 0.0.38 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index 49d43fe026..54c5bb59a1 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.0.38", + "version": "0.0.39-next.1", "backstage": { "role": "backend" }, diff --git a/packages/canon/CHANGELOG.md b/packages/canon/CHANGELOG.md index b53f1dc3a0..ab6fb4ff00 100644 --- a/packages/canon/CHANGELOG.md +++ b/packages/canon/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/canon +## 0.5.0-next.1 + +### Minor Changes + +- 621fac9: We are updating the default size of the Button component in Canon to be small instead of medium. +- a842554: We set the default size for IconButton in Canon to be small instead of medium. + +## 0.5.0-next.0 + +### Minor Changes + +- 24b45ef: Fixes spacing props on layout components and aligned on naming for the Grid component. You should now call the Grid root component using instead of just . + +### Patch Changes + +- 269316d: Remove leftover console.log from Container component. + ## 0.4.0 ### Minor Changes diff --git a/packages/canon/package.json b/packages/canon/package.json index db10d0f967..ab9193806a 100644 --- a/packages/canon/package.json +++ b/packages/canon/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/canon", - "version": "0.4.0", + "version": "0.5.0-next.1", "backstage": { "role": "web-library" }, diff --git a/packages/canon/src/components/Button/Button.stories.tsx b/packages/canon/src/components/Button/Button.stories.tsx index fffc31b488..c0519cb279 100644 --- a/packages/canon/src/components/Button/Button.stories.tsx +++ b/packages/canon/src/components/Button/Button.stories.tsx @@ -34,15 +34,17 @@ const meta = { options: ['primary', 'secondary'], }, }, - args: { - size: 'medium', - variant: 'primary', - }, } satisfies Meta; export default meta; type Story = StoryObj; +export const Default: Story = { + args: { + children: 'Button', + }, +}; + export const Variants: Story = { args: { children: 'Button', @@ -72,12 +74,12 @@ export const Sizes: Story = { }, render: () => ( - + ), }; diff --git a/packages/canon/src/components/Button/Button.tsx b/packages/canon/src/components/Button/Button.tsx index ab70f168c8..e9774ad083 100644 --- a/packages/canon/src/components/Button/Button.tsx +++ b/packages/canon/src/components/Button/Button.tsx @@ -24,7 +24,7 @@ import type { ButtonProps } from './types'; export const Button = forwardRef( (props: ButtonProps, ref) => { const { - size = 'medium', + size = 'small', variant = 'primary', disabled, iconStart, diff --git a/packages/canon/src/components/Container/Container.tsx b/packages/canon/src/components/Container/Container.tsx index e670a61a21..ebdc17ac85 100644 --- a/packages/canon/src/components/Container/Container.tsx +++ b/packages/canon/src/components/Container/Container.tsx @@ -42,8 +42,6 @@ export const Container = forwardRef( }; const { className, style } = extractProps(props, propDefs); - console.log(className, style); - return createElement('div', { ref, className: clsx('canon-Container', className), diff --git a/packages/canon/src/components/IconButton/IconButton.stories.tsx b/packages/canon/src/components/IconButton/IconButton.stories.tsx index d16dcd1fcb..7a7272ad05 100644 --- a/packages/canon/src/components/IconButton/IconButton.stories.tsx +++ b/packages/canon/src/components/IconButton/IconButton.stories.tsx @@ -34,29 +34,23 @@ const meta = { options: ['primary', 'secondary'], }, }, - args: { - size: 'medium', - variant: 'primary', - }, } satisfies Meta; export default meta; type Story = StoryObj; -export const Variants: Story = { +export const Default: Story = { args: { icon: , - 'aria-label': 'Cloud icon button', }, - parameters: { - argTypes: { - variant: { - control: false, - }, - }, +}; + +export const Variants: Story = { + args: { + ...Default.args, }, render: args => ( - + @@ -66,12 +60,11 @@ export const Variants: Story = { export const Sizes: Story = { args: { icon: , - 'aria-label': 'Cloud icon button', }, render: args => ( - - + + ), }; @@ -83,7 +76,7 @@ export const Disabled: Story = { 'aria-label': 'Cloud icon button', }, render: args => ( - + diff --git a/packages/canon/src/components/IconButton/IconButton.tsx b/packages/canon/src/components/IconButton/IconButton.tsx index bca012cac0..fd80c91ddd 100644 --- a/packages/canon/src/components/IconButton/IconButton.tsx +++ b/packages/canon/src/components/IconButton/IconButton.tsx @@ -24,7 +24,7 @@ import type { IconButtonProps } from './types'; export const IconButton = forwardRef( (props: IconButtonProps, ref) => { const { - size = 'medium', + size = 'small', variant = 'primary', icon, className, diff --git a/packages/catalog-client/CHANGELOG.md b/packages/catalog-client/CHANGELOG.md index a86ac71928..d521818703 100644 --- a/packages/catalog-client/CHANGELOG.md +++ b/packages/catalog-client/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/catalog-client +## 1.10.1-next.0 + +### Patch Changes + +- 22fad0d: Fixed `CatalogClient` error responses for `refreshEntity` and `addLocation`. +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/errors@1.2.7 + ## 1.10.0 ### Minor Changes diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index 0f3ff58c4a..b87a3d4348 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-client", - "version": "1.10.0", + "version": "1.10.1-next.0", "description": "An isomorphic client for the catalog backend", "backstage": { "role": "common-library" diff --git a/packages/catalog-client/src/CatalogClient.ts b/packages/catalog-client/src/CatalogClient.ts index b3f8df1aa6..e69f6ad434 100644 --- a/packages/catalog-client/src/CatalogClient.ts +++ b/packages/catalog-client/src/CatalogClient.ts @@ -305,7 +305,7 @@ export class CatalogClient implements CatalogApi { ); if (response.status !== 200) { - throw new Error(await response.text()); + throw await ResponseError.fromResponse(response); } } @@ -345,7 +345,7 @@ export class CatalogClient implements CatalogApi { ); if (response.status !== 201) { - throw new Error(await response.text()); + throw await ResponseError.fromResponse(response); } const { location, entities, exists } = await response.json(); diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index e532f4130a..7e03186e23 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/cli +## 0.32.2-next.0 + +### Patch Changes + +- ce70439: The `BACKSTAGE_CLI_EXPERIMENTAL_BUILD_CACHE` flag has been removed. Existing users are encouraged to switch to `EXPERIMENTAL_RSPACK` instead. +- 1d8f00b: Switched to using the `ModuleFederationPlugin` from `@module-federation/enhanced/rspack` for Rspack, rather than the built-in one. +- 2b9633f: The experimental `FORCE_REACT_DEVELOPMENT` flag has been removed. +- d8c4a54: Only use the caching Jest module loader for frontend packages in order to avoid breaking real ESM module imports. +- 1bab255: Internal refactor to combine alpha `build` and `start` modules. +- 713e957: fix: merge eslint reports when using json format +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/cli-common@0.1.15 + - @backstage/cli-node@0.2.13 + - @backstage/config@1.3.2 + - @backstage/config-loader@1.10.1 + - @backstage/errors@1.2.7 + - @backstage/eslint-plugin@0.1.10 + - @backstage/integration@1.17.0 + - @backstage/release-manifests@0.0.13 + - @backstage/types@1.2.1 + ## 0.32.1 ### Patch Changes diff --git a/packages/cli/config/jest.js b/packages/cli/config/jest.js index 560c9bf708..f011d14b19 100644 --- a/packages/cli/config/jest.js +++ b/packages/cli/config/jest.js @@ -212,6 +212,11 @@ function getRoleConfig(role, pkgJson) { if (FRONTEND_ROLES.includes(role)) { return { testEnvironment: require.resolve('jest-environment-jsdom'), + // The caching module loader is only used to speed up frontend tests, + // as it breaks real dynamic imports of ESM modules. + runtime: envOptions.oldTests + ? undefined + : require.resolve('./jestCachingModuleLoader'), transform, }; } @@ -257,10 +262,6 @@ async function getProjectConfig(targetPath, extraConfig, extraOptions) { // A bit more opinionated testMatch: [`**/*.test.{${SRC_EXTS.join(',')}}`], - runtime: envOptions.oldTests - ? undefined - : require.resolve('./jestCachingModuleLoader'), - transformIgnorePatterns: [`/node_modules/(?:${transformIgnorePattern})/`], ...getRoleConfig(pkgJson.backstage?.role, pkgJson), }; diff --git a/packages/cli/package.json b/packages/cli/package.json index 32cf2dfd3c..603d1a4e1a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/cli", - "version": "0.32.1", + "version": "0.32.2-next.0", "description": "CLI for developing Backstage plugins and apps", "backstage": { "role": "cli" diff --git a/packages/cli/src/alpha.ts b/packages/cli/src/alpha.ts index a29594e135..f7333073d8 100644 --- a/packages/cli/src/alpha.ts +++ b/packages/cli/src/alpha.ts @@ -33,7 +33,6 @@ import chalk from 'chalk'; initializer.add(import('./modules/maintenance/alpha')); initializer.add(import('./modules/migrate/alpha')); initializer.add(import('./modules/new/alpha')); - initializer.add(import('./modules/start/alpha')); initializer.add(import('./modules/test/alpha')); await initializer.run(); })(); diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 8483fd79c0..8cfad48b14 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -21,10 +21,6 @@ import { registerRepoCommands as registerRepoBuildCommands, registerCommands as registerBuildCommands, } from '../modules/build'; -import { - registerPackageCommands as registerPackageStartCommands, - registerRepoCommands as registerRepoStartCommands, -} from '../modules/start'; import { registerCommands as registerInfoCommands } from '../modules/info'; import { registerCommands as registerMigrateCommand } from '../modules/migrate'; import { @@ -48,7 +44,6 @@ export function registerRepoCommand(program: Command) { .command('repo [command]') .description('Command that run across an entire Backstage project'); - registerRepoStartCommands(command); registerRepoBuildCommands(command); registerRepoTestCommands(command); registerRepoLintCommands(command); @@ -60,7 +55,6 @@ export function registerScriptCommand(program: Command) { .command('package [command]') .description('Lifecycle scripts for individual packages'); - registerPackageStartCommands(command); registerPackageBuildCommands(command); registerPackageTestCommands(command); registerMaintenancePackageCommands(command); diff --git a/packages/cli/src/modules/build/alpha.ts b/packages/cli/src/modules/build/alpha.ts index 5e2c6d6e7d..fb9365d7c5 100644 --- a/packages/cli/src/modules/build/alpha.ts +++ b/packages/cli/src/modules/build/alpha.ts @@ -18,6 +18,7 @@ import { Command, Option } from 'commander'; import { createCliPlugin } from '../../wiring/factory'; import { lazy } from '../../lib/lazy'; import { registerPackageCommands } from '.'; +import { configOption } from '../config'; export const buildPlugin = createCliPlugin({ pluginId: 'build', @@ -85,6 +86,80 @@ export const buildPlugin = createCliPlugin({ }, }); + reg.addCommand({ + path: ['package', 'start'], + description: 'Start a package for local development', + execute: async ({ args }) => { + const command = new Command(); + + const defaultCommand = command + .option(...configOption) + .option( + '--role ', + 'Run the command with an explicit package role', + ) + .option('--check', 'Enable type checking and linting if available') + .option('--inspect [host]', 'Enable debugger in Node.js environments') + .option( + '--inspect-brk [host]', + 'Enable debugger in Node.js environments, breaking before code starts', + ) + .option( + '--require ', + 'Add a --require argument to the node process', + ) + .option( + '--link ', + 'Link an external workspace for module resolution', + ) + .action(lazy(() => import('./commands/package/start'), 'command')); + + await defaultCommand.parseAsync(args, { from: 'user' }); + }, + }); + + reg.addCommand({ + path: ['repo', 'start'], + description: 'Starts packages in the repo for local development', + execute: async ({ args }) => { + const command = new Command(); + + const defaultCommand = command + .argument( + '[...packageNameOrPath]', + 'Run the specified package instead of the defaults.', + ) + .option( + '--plugin ', + 'Start the dev entry-point for any matching plugin package in the repo', + (opt: string, opts: string[]) => (opts ? [...opts, opt] : [opt]), + Array(), + ) + .option(...configOption) + .option( + '--inspect [host]', + 'Enable debugger in Node.js environments. Applies to backend package only', + ) + .option( + '--inspect-brk [host]', + 'Enable debugger in Node.js environments, breaking before code starts. Applies to backend package only', + ) + .option( + '--require ', + 'Add a --require argument to the node process. Applies to backend package only', + ) + .option( + '--link ', + 'Link an external workspace for module resolution', + ) + .action( + lazy(() => import('../build/commands/repo/start'), 'command'), + ); + + await defaultCommand.parseAsync(args, { from: 'user' }); + }, + }); + reg.addCommand({ path: ['build-workspace'], description: diff --git a/packages/cli/src/modules/start/commands/package/start/command.ts b/packages/cli/src/modules/build/commands/package/start/command.ts similarity index 100% rename from packages/cli/src/modules/start/commands/package/start/command.ts rename to packages/cli/src/modules/build/commands/package/start/command.ts diff --git a/packages/cli/src/modules/start/commands/package/start/index.ts b/packages/cli/src/modules/build/commands/package/start/index.ts similarity index 100% rename from packages/cli/src/modules/start/commands/package/start/index.ts rename to packages/cli/src/modules/build/commands/package/start/index.ts diff --git a/packages/cli/src/modules/start/commands/package/start/resolveLinkedWorkspace.ts b/packages/cli/src/modules/build/commands/package/start/resolveLinkedWorkspace.ts similarity index 100% rename from packages/cli/src/modules/start/commands/package/start/resolveLinkedWorkspace.ts rename to packages/cli/src/modules/build/commands/package/start/resolveLinkedWorkspace.ts diff --git a/packages/cli/src/modules/start/commands/package/start/startBackend.ts b/packages/cli/src/modules/build/commands/package/start/startBackend.ts similarity index 100% rename from packages/cli/src/modules/start/commands/package/start/startBackend.ts rename to packages/cli/src/modules/build/commands/package/start/startBackend.ts diff --git a/packages/cli/src/modules/start/commands/package/start/startFrontend.ts b/packages/cli/src/modules/build/commands/package/start/startFrontend.ts similarity index 100% rename from packages/cli/src/modules/start/commands/package/start/startFrontend.ts rename to packages/cli/src/modules/build/commands/package/start/startFrontend.ts diff --git a/packages/cli/src/modules/start/commands/package/start/startPackage.ts b/packages/cli/src/modules/build/commands/package/start/startPackage.ts similarity index 100% rename from packages/cli/src/modules/start/commands/package/start/startPackage.ts rename to packages/cli/src/modules/build/commands/package/start/startPackage.ts diff --git a/packages/cli/src/modules/start/commands/repo/start.test.ts b/packages/cli/src/modules/build/commands/repo/start.test.ts similarity index 100% rename from packages/cli/src/modules/start/commands/repo/start.test.ts rename to packages/cli/src/modules/build/commands/repo/start.test.ts diff --git a/packages/cli/src/modules/start/commands/repo/start.ts b/packages/cli/src/modules/build/commands/repo/start.ts similarity index 100% rename from packages/cli/src/modules/start/commands/repo/start.ts rename to packages/cli/src/modules/build/commands/repo/start.ts diff --git a/packages/cli/src/modules/build/index.ts b/packages/cli/src/modules/build/index.ts index 0135517cd8..c859985975 100644 --- a/packages/cli/src/modules/build/index.ts +++ b/packages/cli/src/modules/build/index.ts @@ -15,6 +15,7 @@ */ import { Command, Option } from 'commander'; import { lazy } from '../../lib/lazy'; +import { configOption } from '../config'; export function registerRepoCommands(command: Command) { command @@ -35,6 +36,35 @@ export function registerRepoCommands(command: Command) { 'Minify the generated code. Does not apply to app package (app is minified by default).', ) .action(lazy(() => import('./commands/repo/build'), 'command')); + + command + .command('start') + .description('Starts packages in the repo for local development') + .argument( + '[packageNameOrPath...]', + 'Run the specified package instead of the defaults.', + ) + .option( + '--plugin ', + 'Start the dev entry-point for any matching plugin package in the repo', + (opt: string, opts: string[]) => (opts ? [...opts, opt] : [opt]), + Array(), + ) + .option(...configOption) + .option( + '--inspect [host]', + 'Enable debugger in Node.js environments. Applies to backend package only', + ) + .option( + '--inspect-brk [host]', + 'Enable debugger in Node.js environments, breaking before code starts. Applies to backend package only', + ) + .option( + '--require ', + 'Add a --require argument to the node process. Applies to backend package only', + ) + .option('--link ', 'Link an external workspace for module resolution') + .action(lazy(() => import('../build/commands/repo/start'), 'command')); } export function registerPackageCommands(command: Command) { @@ -61,6 +91,24 @@ export function registerPackageCommands(command: Command) { Array(), ) .action(lazy(() => import('./commands/package/build'), 'command')); + + command + .command('start') + .description('Start a package for local development') + .option(...configOption) + .option('--role ', 'Run the command with an explicit package role') + .option('--check', 'Enable type checking and linting if available') + .option('--inspect [host]', 'Enable debugger in Node.js environments') + .option( + '--inspect-brk [host]', + 'Enable debugger in Node.js environments, breaking before code starts', + ) + .option( + '--require ', + 'Add a --require argument to the node process', + ) + .option('--link ', 'Link an external workspace for module resolution') + .action(lazy(() => import('./commands/package/start'), 'command')); } export function registerCommands(program: Command) { diff --git a/packages/cli/src/modules/build/lib/bundler/config.ts b/packages/cli/src/modules/build/lib/bundler/config.ts index d7c824e3fa..655ce4fd8b 100644 --- a/packages/cli/src/modules/build/lib/bundler/config.ts +++ b/packages/cli/src/modules/build/lib/bundler/config.ts @@ -15,8 +15,7 @@ */ import { BundlingOptions, ModuleFederationOptions } from './types'; -import { resolve as resolvePath, dirname } from 'path'; -import chalk from 'chalk'; +import { resolve as resolvePath } from 'path'; import webpack from 'webpack'; import { BundlingPaths } from './paths'; @@ -39,8 +38,6 @@ import { hasReactDomClient } from './hasReactDomClient'; import { createWorkspaceLinkingPlugins } from './linkWorkspaces'; import { ConfigInjectingHtmlWebpackPlugin } from './ConfigInjectingHtmlWebpackPlugin'; -const BUILD_CACHE_ENV_VAR = 'BACKSTAGE_CLI_EXPERIMENTAL_BUILD_CACHE'; - export function resolveBaseUrl( config: Config, moduleFederation?: ModuleFederationOptions, @@ -231,8 +228,8 @@ export async function createConfig( const isRemote = options.moduleFederation?.mode === 'remote'; const AdaptedModuleFederationPlugin = rspack - ? (rspack.container - .ModuleFederationPlugin as unknown as typeof ModuleFederationPlugin) + ? (require('@module-federation/enhanced/rspack') + .ModuleFederationPlugin as typeof ModuleFederationPlugin) : ModuleFederationPlugin; const exposes = options.moduleFederation?.exposes @@ -348,49 +345,6 @@ export async function createConfig( const mode = isDev ? 'development' : 'production'; const optimization = optimizationConfig(options); - if ( - mode === 'production' && - process.env.EXPERIMENTAL_MODULE_FEDERATION && - process.env.FORCE_REACT_DEVELOPMENT - ) { - console.log( - chalk.yellow( - `⚠️ WARNING: Forcing react and react-dom into development mode. This build should not be used in production.`, - ), - ); - - const reactPackageDirs = [ - `${dirname(require.resolve('react/package.json'))}/`, - `${dirname(require.resolve('react-dom/package.json'))}/`, - ]; - - // Don't define process.env.NODE_ENV with value matching config.mode. If we - // don't set this to false, webpack will define the value of - // process.env.NODE_ENV for us, and the definition below will be ignored. - optimization.nodeEnv = false; - - // Instead, provide a custom definition which always uses "development" if - // the module is part of `react` or `react-dom`, and `config.mode` otherwise. - plugins.push( - new bundler.DefinePlugin({ - 'process.env.NODE_ENV': rspack - ? // FIXME: see also https://github.com/web-infra-dev/rspack/issues/5606 - JSON.stringify(mode) - : webpack.DefinePlugin.runtimeValue(({ module }) => { - if ( - reactPackageDirs.some(val => module.resource.startsWith(val)) - ) { - return '"development"'; - } - - return `"${mode}"`; - }), - }), - ); - } - - const withCache = yn(process.env[BUILD_CACHE_ENV_VAR], { default: false }); - return { mode, profile: false, @@ -473,13 +427,5 @@ export async function createConfig( }), }, plugins, - ...(withCache && { - cache: { - type: 'filesystem', - buildDependencies: { - config: [__filename], - }, - }, - }), }; } diff --git a/packages/cli/src/modules/start/lib/ipc/IpcServer.ts b/packages/cli/src/modules/build/lib/ipc/IpcServer.ts similarity index 100% rename from packages/cli/src/modules/start/lib/ipc/IpcServer.ts rename to packages/cli/src/modules/build/lib/ipc/IpcServer.ts diff --git a/packages/cli/src/modules/start/lib/ipc/ServerDataStore.ts b/packages/cli/src/modules/build/lib/ipc/ServerDataStore.ts similarity index 100% rename from packages/cli/src/modules/start/lib/ipc/ServerDataStore.ts rename to packages/cli/src/modules/build/lib/ipc/ServerDataStore.ts diff --git a/packages/cli/src/modules/start/lib/ipc/index.ts b/packages/cli/src/modules/build/lib/ipc/index.ts similarity index 100% rename from packages/cli/src/modules/start/lib/ipc/index.ts rename to packages/cli/src/modules/build/lib/ipc/index.ts diff --git a/packages/cli/src/modules/start/lib/runner/index.ts b/packages/cli/src/modules/build/lib/runner/index.ts similarity index 100% rename from packages/cli/src/modules/start/lib/runner/index.ts rename to packages/cli/src/modules/build/lib/runner/index.ts diff --git a/packages/cli/src/modules/start/lib/runner/runBackend.ts b/packages/cli/src/modules/build/lib/runner/runBackend.ts similarity index 100% rename from packages/cli/src/modules/start/lib/runner/runBackend.ts rename to packages/cli/src/modules/build/lib/runner/runBackend.ts diff --git a/packages/cli/src/modules/config/commands/docs.ts b/packages/cli/src/modules/config/commands/docs.ts index 25e3a864f4..e7d3ac13ee 100644 --- a/packages/cli/src/modules/config/commands/docs.ts +++ b/packages/cli/src/modules/config/commands/docs.ts @@ -19,6 +19,7 @@ import { mergeConfigSchemas } from '@backstage/config-loader'; import { OptionValues } from 'commander'; import { JSONSchema7 as JSONSchema } from 'json-schema'; import openBrowser from 'react-dev-utils/openBrowser'; +import chalk from 'chalk'; import { loadCliConfig } from '../lib/config'; const DOCS_URL = 'https://config.backstage.io'; @@ -36,5 +37,23 @@ export default async (opts: OptionValues) => { ), ); - openBrowser(`${DOCS_URL}#schema=${JSON.stringify(schema)}`); + const url = `${DOCS_URL}#schema=${JSON.stringify(schema)}`; + + console.log(); + console.log( + chalk.cyan( + 'Opening configuration reference documentation in your browser...', + ), + ); + console.log(` ${chalk.cyan(url)}`); + console.log(); + + const opened = openBrowser(url); + + if (!opened) { + console.log( + chalk.yellow('⚠️ WARNING: Unable to open browser automatically.'), + ); + console.log(chalk.yellow('Please open the URL manually in your browser.')); + } }; diff --git a/packages/cli/src/modules/lint/commands/repo/lint.ts b/packages/cli/src/modules/lint/commands/repo/lint.ts index 72bb02a693..f433740886 100644 --- a/packages/cli/src/modules/lint/commands/repo/lint.ts +++ b/packages/cli/src/modules/lint/commands/repo/lint.ts @@ -220,6 +220,7 @@ export async function command(opts: OptionValues, cmd: Command): Promise { }); const outputSuccessCache = []; + const jsonResults = []; let errorOutput = ''; @@ -238,7 +239,11 @@ export async function command(opts: OptionValues, cmd: Command): Promise { // dump of all warnings that might be irrelevant if (resultText) { if (opts.outputFile) { - errorOutput += `${resultText}\n`; + if (opts.format === 'json') { + jsonResults.push(resultText); + } else { + errorOutput += `${resultText}\n`; + } } else { console.log(); console.log(resultText.trimStart()); @@ -249,6 +254,14 @@ export async function command(opts: OptionValues, cmd: Command): Promise { } } + if (opts.format === 'json') { + let mergedJsonResults: any[] = []; + for (const jsonResult of jsonResults) { + mergedJsonResults = mergedJsonResults.concat(JSON.parse(jsonResult)); + } + errorOutput = JSON.stringify(mergedJsonResults, null, 2); + } + if (opts.outputFile && errorOutput) { await fs.writeFile(paths.resolveTargetRoot(opts.outputFile), errorOutput); } diff --git a/packages/cli/src/modules/start/alpha.ts b/packages/cli/src/modules/start/alpha.ts deleted file mode 100644 index a5875df335..0000000000 --- a/packages/cli/src/modules/start/alpha.ts +++ /dev/null @@ -1,99 +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 { Command } from 'commander'; -import { createCliPlugin } from '../../wiring/factory'; -import { lazy } from '../../lib/lazy'; -import { configOption } from '../config'; - -export const startPlugin = createCliPlugin({ - pluginId: 'start', - init: async reg => { - reg.addCommand({ - path: ['package', 'start'], - description: 'Start a package for local development', - execute: async ({ args }) => { - const command = new Command(); - - const defaultCommand = command - .option(...configOption) - .option( - '--role ', - 'Run the command with an explicit package role', - ) - .option('--check', 'Enable type checking and linting if available') - .option('--inspect [host]', 'Enable debugger in Node.js environments') - .option( - '--inspect-brk [host]', - 'Enable debugger in Node.js environments, breaking before code starts', - ) - .option( - '--require ', - 'Add a --require argument to the node process', - ) - .option( - '--link ', - 'Link an external workspace for module resolution', - ) - .action(lazy(() => import('./commands/package/start'), 'command')); - - await defaultCommand.parseAsync(args, { from: 'user' }); - }, - }); - - reg.addCommand({ - path: ['repo', 'start'], - description: 'Starts packages in the repo for local development', - execute: async ({ args }) => { - const command = new Command(); - - const defaultCommand = command - .argument( - '[...packageNameOrPath]', - 'Run the specified package instead of the defaults.', - ) - .option( - '--plugin ', - 'Start the dev entry-point for any matching plugin package in the repo', - (opt: string, opts: string[]) => (opts ? [...opts, opt] : [opt]), - Array(), - ) - .option(...configOption) - .option( - '--inspect [host]', - 'Enable debugger in Node.js environments. Applies to backend package only', - ) - .option( - '--inspect-brk [host]', - 'Enable debugger in Node.js environments, breaking before code starts. Applies to backend package only', - ) - .option( - '--require ', - 'Add a --require argument to the node process. Applies to backend package only', - ) - .option( - '--link ', - 'Link an external workspace for module resolution', - ) - .action(lazy(() => import('./commands/repo/start'), 'command')); - - await defaultCommand.parseAsync(args, { from: 'user' }); - }, - }); - }, -}); - -export default startPlugin; diff --git a/packages/cli/src/modules/start/index.ts b/packages/cli/src/modules/start/index.ts deleted file mode 100644 index 17225820f7..0000000000 --- a/packages/cli/src/modules/start/index.ts +++ /dev/null @@ -1,69 +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 { Command } from 'commander'; -import { lazy } from '../../lib/lazy'; -import { configOption } from '../config'; - -export function registerRepoCommands(command: Command) { - command - .command('start') - .description('Starts packages in the repo for local development') - .argument( - '[packageNameOrPath...]', - 'Run the specified package instead of the defaults.', - ) - .option( - '--plugin ', - 'Start the dev entry-point for any matching plugin package in the repo', - (opt: string, opts: string[]) => (opts ? [...opts, opt] : [opt]), - Array(), - ) - .option(...configOption) - .option( - '--inspect [host]', - 'Enable debugger in Node.js environments. Applies to backend package only', - ) - .option( - '--inspect-brk [host]', - 'Enable debugger in Node.js environments, breaking before code starts. Applies to backend package only', - ) - .option( - '--require ', - 'Add a --require argument to the node process. Applies to backend package only', - ) - .option('--link ', 'Link an external workspace for module resolution') - .action(lazy(() => import('./commands/repo/start'), 'command')); -} - -export function registerPackageCommands(command: Command) { - command - .command('start') - .description('Start a package for local development') - .option(...configOption) - .option('--role ', 'Run the command with an explicit package role') - .option('--check', 'Enable type checking and linting if available') - .option('--inspect [host]', 'Enable debugger in Node.js environments') - .option( - '--inspect-brk [host]', - 'Enable debugger in Node.js environments, breaking before code starts', - ) - .option( - '--require ', - 'Add a --require argument to the node process', - ) - .option('--link ', 'Link an external workspace for module resolution') - .action(lazy(() => import('./commands/package/start'), 'command')); -} diff --git a/packages/cli/templates/scaffolder-backend-module/src/actions/example.ts b/packages/cli/templates/scaffolder-backend-module/src/actions/example.ts index 7e47a89bcc..9ce24f92bc 100644 --- a/packages/cli/templates/scaffolder-backend-module/src/actions/example.ts +++ b/packages/cli/templates/scaffolder-backend-module/src/actions/example.ts @@ -12,22 +12,16 @@ import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; export function createExampleAction() { // For more information on how to define custom actions, see // https://backstage.io/docs/features/software-templates/writing-custom-actions - return createTemplateAction<{ - myParameter: string; - }>({ + return createTemplateAction({ id: 'acme:example', description: 'Runs an example action', schema: { input: { - type: 'object', - required: ['myParameter'], - properties: { - myParameter: { - title: 'An example parameter', - description: "This is an example parameter, don't set it to foo", - type: 'string', - }, - }, + myParameter: z => + z.string({ + description: + "This is an example parameter, don't set it to foo", + }), }, }, async handler(ctx) { diff --git a/packages/core-compat-api/CHANGELOG.md b/packages/core-compat-api/CHANGELOG.md index ceee44d109..e1919518ea 100644 --- a/packages/core-compat-api/CHANGELOG.md +++ b/packages/core-compat-api/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/core-compat-api +## 0.4.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/version-bridge@1.0.11 + +## 0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + ## 0.4.2 ### Patch Changes diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json index b491f18b69..db865fbec9 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.4.2", + "version": "0.4.3-next.1", "backstage": { "role": "web-library" }, diff --git a/packages/core-compat-api/src/convertLegacyPlugin.test.tsx b/packages/core-compat-api/src/convertLegacyPlugin.test.tsx index 68bb7cac3f..3f85e07ca5 100644 --- a/packages/core-compat-api/src/convertLegacyPlugin.test.tsx +++ b/packages/core-compat-api/src/convertLegacyPlugin.test.tsx @@ -41,6 +41,8 @@ describe('convertLegacyPlugin', () => { "featureFlags": [], "getExtension": [Function], "id": "test", + "info": [Function], + "infoOptions": undefined, "routes": {}, "toString": [Function], "version": "v1", diff --git a/packages/core-components/report-alpha.api.md b/packages/core-components/report-alpha.api.md index 73b4194266..67c4226322 100644 --- a/packages/core-components/report-alpha.api.md +++ b/packages/core-components/report-alpha.api.md @@ -13,6 +13,7 @@ export const coreComponentsTranslationRef: TranslationRef< readonly 'table.filter.placeholder': 'All results'; readonly 'table.filter.clearAll': 'Clear all'; readonly 'table.body.emptyDataSourceMessage': 'No records to display'; + readonly 'table.header.actions': 'Actions'; readonly 'table.toolbar.search': 'Filter'; readonly 'table.pagination.firstTooltip': 'First Page'; readonly 'table.pagination.labelDisplayedRows': '{from}-{to} of {count}'; diff --git a/packages/core-components/src/components/Table/Table.tsx b/packages/core-components/src/components/Table/Table.tsx index 08fc746ca4..0287cfdf40 100644 --- a/packages/core-components/src/components/Table/Table.tsx +++ b/packages/core-components/src/components/Table/Table.tsx @@ -509,6 +509,10 @@ export function Table(props: TableProps) { previousTooltip: t('table.pagination.previousTooltip'), ...localization?.pagination, }, + header: { + actions: t('table.header.actions'), + ...localization?.header, + }, toolbar: { searchPlaceholder: t('table.toolbar.search'), searchTooltip: t('table.toolbar.search'), diff --git a/packages/core-components/src/translation.ts b/packages/core-components/src/translation.ts index 40b6e94798..8313b75a40 100644 --- a/packages/core-components/src/translation.ts +++ b/packages/core-components/src/translation.ts @@ -104,6 +104,9 @@ export const coreComponentsTranslationRef = createTranslationRef({ toolbar: { search: 'Filter', }, + header: { + actions: 'Actions', + }, }, alertDisplay: { message_one: '({{ count }} newer message)', diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index dfb2c91734..2876e57911 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/create-app +## 0.7.0-next.1 + +### Patch Changes + +- Bumped create-app version. +- Updated dependencies + - @backstage/cli-common@0.1.15 + +## 0.7.0-next.0 + +### Minor Changes + +- 30474c4: Add .cache directory to shipped gitignore. + ## 0.6.2 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 50559d8f52..735d08cc64 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/create-app", - "version": "0.6.2", + "version": "0.7.0-next.1", "description": "A CLI that helps you create your own Backstage app", "backstage": { "role": "cli" diff --git a/packages/create-app/templates/default-app/.gitignore.hbs b/packages/create-app/templates/default-app/.gitignore.hbs index e506e00f92..2164e72f7d 100644 --- a/packages/create-app/templates/default-app/.gitignore.hbs +++ b/packages/create-app/templates/default-app/.gitignore.hbs @@ -51,3 +51,6 @@ site # E2E test reports e2e-test-report/ + +# Cache +.cache/ diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index 5a8e87d582..572ba74a94 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/dev-utils +## 1.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/app-defaults@1.6.2 + - @backstage/catalog-model@1.7.4 + - @backstage/core-app-api@1.17.0 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/integration-react@1.2.7 + - @backstage/theme@0.6.6 + +## 1.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.18.1-next.0 + - @backstage/integration-react@1.2.7 + ## 1.1.10 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 056e7480b6..545f249f06 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/dev-utils", - "version": "1.1.10", + "version": "1.1.11-next.1", "description": "Utilities for developing Backstage plugins.", "backstage": { "role": "web-library" diff --git a/packages/e2e-test/CHANGELOG.md b/packages/e2e-test/CHANGELOG.md index 0017386e26..4b4da9d410 100644 --- a/packages/e2e-test/CHANGELOG.md +++ b/packages/e2e-test/CHANGELOG.md @@ -1,5 +1,21 @@ # e2e-test +## 0.2.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.7.0-next.1 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + +## 0.2.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.7.0-next.0 + ## 0.2.28 ### Patch Changes diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json index e181990096..5f890a8b5e 100644 --- a/packages/e2e-test/package.json +++ b/packages/e2e-test/package.json @@ -1,6 +1,6 @@ { "name": "e2e-test", - "version": "0.2.28", + "version": "0.2.29-next.1", "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 c6a4a68f9f..c7e7c4cd46 100644 --- a/packages/frontend-app-api/CHANGELOG.md +++ b/packages/frontend-app-api/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/frontend-app-api +## 0.11.3-next.0 + +### Patch Changes + +- c38c9e8: Implemented support for the `plugin.info()` method in specialized apps with a default resolved for `package.json` and `catalog-info.yaml`. The default resolution logic can be overridden via the `pluginInfoResolver` option to `createSpecializedApp`, and plugin-specific overrides can be applied via the new `app.pluginOverrides` key in static configuration. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/frontend-defaults@0.2.3-next.0 + ## 0.11.2 ### Patch Changes diff --git a/packages/frontend-app-api/config.d.ts b/packages/frontend-app-api/config.d.ts index fc265e8833..3b86b5c1d4 100644 --- a/packages/frontend-app-api/config.d.ts +++ b/packages/frontend-app-api/config.d.ts @@ -51,5 +51,71 @@ export interface Config { }; } >; + + /** + * This section enables you to override certain properties of specific or + * groups of plugins. + * + * @remarks + * All matching entries will be applied to each plugin, with the later + * entries taking precedence. + * + * This configuration is intended to be used primarily to apply overrides + * for third-party plugins. + * + * @deepVisibility frontend + */ + pluginOverrides?: Array<{ + /** + * The criteria for matching plugins to override. + * + * @remarks + * If no match criteria are provided, the override will be applied to + * all plugins. + */ + match?: { + /** + * A pattern that is matched against the plugin ID. + * + * @remarks + * By default the string is interpreted as a glob pattern, but if the + * string is surrounded by '/' it is interpreted as a regex. + */ + pluginId?: string; + + /** + * A pattern that is matched against the package name. + * + * @remarks + * By default the string is interpreted as a glob pattern, but if the + * string is surrounded by '/' it is interpreted as a regex. + * + * Note that this will only work for plugins that provide a + * `package.json` info loader. + */ + packageName?: string; + }; + /** + * Overrides individual top-level fields of the plugin info. + */ + info: { + /** + * Override the description of the plugin. + */ + description?: string; + /** + * Override the owner entity references of the plugin. + * + * @remarks + * The provided values are interpreted as entity references defaulting + * to Group entities in the default namespace. + */ + ownerEntityRefs?: string[]; + /** + * Override the links of the plugin. + */ + links?: Array<{ title: string; url: string }>; + }; + }>; }; } diff --git a/packages/frontend-app-api/package.json b/packages/frontend-app-api/package.json index 56a57b3bc5..cc5a7fd807 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.11.2", + "version": "0.11.3-next.0", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-app-api/report.api.md b/packages/frontend-app-api/report.api.md index d2a3ac4d3b..7228777561 100644 --- a/packages/frontend-app-api/report.api.md +++ b/packages/frontend-app-api/report.api.md @@ -9,6 +9,8 @@ import { ConfigApi } from '@backstage/core-plugin-api'; import { ExtensionFactoryMiddleware } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; import { FrontendFeature as FrontendFeature_2 } from '@backstage/frontend-plugin-api'; +import { FrontendPluginInfo } from '@backstage/frontend-plugin-api'; +import { JsonObject } from '@backstage/types'; import { RouteRef } from '@backstage/frontend-plugin-api'; import { SubRouteRef } from '@backstage/frontend-plugin-api'; @@ -27,7 +29,7 @@ export type CreateAppRouteBinder = < // @public export function createSpecializedApp(options?: { - features?: FrontendFeature[]; + features?: FrontendFeature_2[]; config?: ConfigApi; bindRoutes?(context: { bind: CreateAppRouteBinder }): void; apis?: ApiHolder; @@ -37,6 +39,7 @@ export function createSpecializedApp(options?: { flags?: { allowUnknownExtensionConfig?: boolean; }; + pluginInfoResolver?: FrontendPluginInfoResolver; }): { apis: ApiHolder; tree: AppTree; @@ -44,4 +47,18 @@ export function createSpecializedApp(options?: { // @public @deprecated (undocumented) export type FrontendFeature = FrontendFeature_2; + +// @public +export type FrontendPluginInfoResolver = (ctx: { + packageJson(): Promise; + manifest(): Promise; + defaultResolver(sources: { + packageJson: JsonObject | undefined; + manifest: JsonObject | undefined; + }): Promise<{ + info: FrontendPluginInfo; + }>; +}) => Promise<{ + info: FrontendPluginInfo; +}>; ``` diff --git a/packages/frontend-app-api/src/wiring/createPluginInfoAttacher.test.ts b/packages/frontend-app-api/src/wiring/createPluginInfoAttacher.test.ts new file mode 100644 index 0000000000..6c053f8aed --- /dev/null +++ b/packages/frontend-app-api/src/wiring/createPluginInfoAttacher.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 { mockApis } from '@backstage/test-utils'; +import { createPluginInfoAttacher } from './createPluginInfoAttacher'; +import { OpaqueFrontendPlugin } from '@internal/frontend'; +import { + createFrontendPlugin, + FrontendFeature, +} from '@backstage/frontend-plugin-api'; + +function getInfo(plugin: FrontendFeature) { + return OpaqueFrontendPlugin.toInternal(plugin).info(); +} + +describe('createPluginInfoAttacher', () => { + const mockConfig = mockApis.config({ + data: { + app: { + pluginOverrides: [ + { + match: { + pluginId: '/^.*-tester$/', + }, + info: { + description: 'Overridden description', + }, + }, + { + match: { + pluginId: '/^not-.*-tester$/', + }, + info: { + ownerEntityRefs: ['test-group'], + }, + }, + { + match: { + packageName: '@test/package', + }, + info: { + description: 'Package name matched', + }, + }, + { + match: { + pluginId: 'info-tester', + }, + info: { + links: [{ title: 'Custom Link', url: 'https://example.com' }], + }, + }, + ], + }, + }, + }); + + describe('with default resolver', () => { + const attacher = createPluginInfoAttacher(mockConfig); + + it('should return a new plugin instance', async () => { + const plugin = createFrontendPlugin({ + pluginId: 'test', + }); + + const newPlugin = attacher(plugin); + expect(newPlugin).not.toBe(plugin); + await expect(getInfo(newPlugin)).resolves.toEqual({}); + }); + + it('should return non-plugin features unchanged', () => { + const nonPluginFeature = { + type: 'not-a-plugin', + } as unknown as FrontendFeature; + + expect(attacher(nonPluginFeature)).toBe(nonPluginFeature); + }); + + it('should resolve plugin info from package.json and config overrides', async () => { + await expect( + getInfo( + attacher( + createFrontendPlugin({ + pluginId: 'other-tester', + info: { + packageJson: async () => ({ + name: '@test/package', + version: '1.0.0', + description: 'Original description', + homepage: 'https://homepage.com', + repository: { + url: 'https://github.com/test/project', + directory: 'packages/test', + }, + }), + }, + }), + ), + ), + ).resolves.toEqual({ + packageName: '@test/package', + version: '1.0.0', + description: 'Package name matched', + links: [ + { + title: 'Homepage', + url: 'https://homepage.com', + }, + { + title: 'Repository', + url: 'https://github.com/test/project/tree/-/packages/test', + }, + ], + }); + + await expect( + getInfo( + attacher( + createFrontendPlugin({ + pluginId: 'info-tester', + info: { + packageJson: async () => ({ + name: '@other/package', + description: 'Original description', + homepage: 'https://homepage.com', + }), + }, + }), + ), + ), + ).resolves.toEqual({ + packageName: '@other/package', + description: 'Overridden description', + links: [ + { + title: 'Custom Link', + url: 'https://example.com', + }, + ], + }); + + await expect( + getInfo( + attacher( + createFrontendPlugin({ + pluginId: 'not-info-tester', + info: { + packageJson: async () => ({ + name: '@other/package', + description: 'Original description', + repository: { + url: 'http://example.com', + directory: 'packages/test', + }, + }), + }, + }), + ), + ), + ).resolves.toEqual({ + packageName: '@other/package', + description: 'Overridden description', + ownerEntityRefs: ['group:default/test-group'], + links: [ + { + title: 'Repository', + url: 'http://example.com/', + }, + ], + }); + }); + }); + + describe('with custom resolver', () => { + const plugin = createFrontendPlugin({ + pluginId: 'custom-resolver', + info: { + packageJson: async () => ({ + name: '@test/resolver', + version: '1.0.0', + }), + manifest: async () => ({ + metadata: { + links: [{ title: 'Metadata link', url: 'https://example.com' }], + }, + }), + }, + }); + + it('should use the default resolver', async () => { + const attacher = createPluginInfoAttacher(mockConfig, async ctx => + ctx.defaultResolver({ + packageJson: await ctx.packageJson(), + manifest: await ctx.manifest(), + }), + ); + + await expect(getInfo(attacher(plugin))).resolves.toEqual({ + packageName: '@test/resolver', + version: '1.0.0', + links: [ + { + title: 'Metadata link', + url: 'https://example.com', + }, + ], + }); + }); + + it('should override info sources passed to default resolver', async () => { + const attacher = createPluginInfoAttacher(mockConfig, ctx => + ctx.defaultResolver({ + packageJson: { + name: '@test/resolver-other', + version: '2.0.0', + }, + manifest: { + metadata: { + links: [{ title: 'Other link', url: 'https://example.com' }], + }, + spec: { + owner: 'test-group', + }, + }, + }), + ); + + await expect(getInfo(attacher(plugin))).resolves.toEqual({ + packageName: '@test/resolver-other', + version: '2.0.0', + links: [ + { + title: 'Other link', + url: 'https://example.com', + }, + ], + ownerEntityRefs: ['group:default/test-group'], + }); + }); + + it('should use a completely custom resolver', async () => { + const attacher = createPluginInfoAttacher(mockConfig, async () => ({ + info: { version: '0.1.0' }, + })); + + await expect(getInfo(attacher(plugin))).resolves.toEqual({ + version: '0.1.0', + }); + }); + + it('should handle unexpected input from the default resolver', async () => { + const attacher = createPluginInfoAttacher(mockConfig, ctx => + ctx.defaultResolver({ + packageJson: { + name: null, + version: {}, + }, + manifest: { + metadata: { + links: 'not an array', + }, + spec: [], + }, + }), + ); + await expect(getInfo(attacher(plugin))).resolves.toEqual({ + version: '[object Object]', + }); + }); + }); +}); diff --git a/packages/frontend-app-api/src/wiring/createPluginInfoAttacher.ts b/packages/frontend-app-api/src/wiring/createPluginInfoAttacher.ts new file mode 100644 index 0000000000..dc2b54f534 --- /dev/null +++ b/packages/frontend-app-api/src/wiring/createPluginInfoAttacher.ts @@ -0,0 +1,247 @@ +/* + * 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 { ConfigApi } from '@backstage/core-plugin-api'; +import { + FrontendFeature, + FrontendPluginInfo, +} from '@backstage/frontend-plugin-api'; +import { OpaqueFrontendPlugin } from '@internal/frontend'; +import { JsonObject, JsonValue } from '@backstage/types'; +import once from 'lodash/once'; +// Avoid full dependency on catalog-model +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { + parseEntityRef, + stringifyEntityRef, +} from '../../../catalog-model/src/entity/ref'; + +/** + * A function that resolves plugin info from a plugin manifest and package.json. + * + * @public + */ +export type FrontendPluginInfoResolver = (ctx: { + packageJson(): Promise; + manifest(): Promise; + defaultResolver(sources: { + packageJson: JsonObject | undefined; + manifest: JsonObject | undefined; + }): Promise<{ info: FrontendPluginInfo }>; +}) => Promise<{ info: FrontendPluginInfo }>; + +export function createPluginInfoAttacher( + config: ConfigApi, + infoResolver: FrontendPluginInfoResolver = async ctx => + ctx.defaultResolver({ + packageJson: await ctx.packageJson(), + manifest: await ctx.manifest(), + }), +): (feature: FrontendFeature) => FrontendFeature { + const applyInfoOverrides = createPluginInfoOverrider(config); + + return (feature: FrontendFeature) => { + if (!OpaqueFrontendPlugin.isType(feature)) { + return feature; + } + + const plugin = OpaqueFrontendPlugin.toInternal(feature); + + return { + ...plugin, + info: once(async () => { + const manifestLoader = plugin.infoOptions?.manifest; + const packageJsonLoader = plugin.infoOptions?.packageJson; + + const { info: resolvedInfo } = await infoResolver({ + manifest: async () => manifestLoader?.(), + packageJson: async () => packageJsonLoader?.(), + defaultResolver: async sources => ({ + info: { + ...resolvePackageInfo(sources.packageJson), + ...resolveManifestInfo(sources.manifest), + }, + }), + }); + + const infoWithOverrides = applyInfoOverrides(plugin.id, resolvedInfo); + return normalizePluginInfo(infoWithOverrides); + }), + }; + }; +} + +function normalizePluginInfo(info: FrontendPluginInfo) { + return { + ...info, + ownerEntityRefs: info.ownerEntityRefs?.map(ref => + stringifyEntityRef( + parseEntityRef(ref, { + defaultKind: 'Group', + }), + ), + ), + }; +} + +function createPluginInfoOverrider(config: ConfigApi) { + const overrideConfigs = + config.getOptionalConfigArray('app.pluginOverrides') ?? []; + + const overrideMatchers = overrideConfigs.map(overrideConfig => { + const pluginIdMatcher = makeStringMatcher( + overrideConfig.getOptionalString('match.pluginId'), + ); + const packageNameMatcher = makeStringMatcher( + overrideConfig.getOptionalString('match.packageName'), + ); + const description = overrideConfig.getOptionalString('info.description'); + const ownerEntityRefs = overrideConfig.getOptionalStringArray( + 'info.ownerEntityRefs', + ); + const links = overrideConfig + .getOptionalConfigArray('info.links') + ?.map(linkConfig => ({ + title: linkConfig.getString('title'), + url: linkConfig.getString('url'), + })); + + return { + test(pluginId: string, packageName?: string) { + return packageNameMatcher(packageName) && pluginIdMatcher(pluginId); + }, + info: { + description, + ownerEntityRefs, + links, + }, + }; + }); + + return (pluginId: string, info: FrontendPluginInfo) => { + const { packageName } = info; + for (const matcher of overrideMatchers) { + if (matcher.test(pluginId, packageName)) { + if (matcher.info.description) { + info.description = matcher.info.description; + } + if (matcher.info.ownerEntityRefs) { + info.ownerEntityRefs = matcher.info.ownerEntityRefs; + } + if (matcher.info.links) { + info.links = matcher.info.links; + } + } + } + return info; + }; +} + +function resolveManifestInfo(manifest?: JsonValue) { + if (!isJsonObject(manifest) || !isJsonObject(manifest.metadata)) { + return undefined; + } + + const info: FrontendPluginInfo = {}; + + if (isJsonObject(manifest.spec) && typeof manifest.spec.owner === 'string') { + info.ownerEntityRefs = [ + stringifyEntityRef( + parseEntityRef(manifest.spec.owner, { + defaultKind: 'Group', + defaultNamespace: manifest.metadata.namespace?.toString(), + }), + ), + ]; + } + + if (Array.isArray(manifest.metadata.links)) { + info.links = manifest.metadata.links.filter(isJsonObject).map(link => ({ + title: String(link.title), + url: String(link.url), + })); + } + + return info; +} + +function resolvePackageInfo(packageJson?: JsonObject) { + if (!packageJson) { + return undefined; + } + + const info: FrontendPluginInfo = { + packageName: packageJson?.name?.toString(), + version: packageJson?.version?.toString(), + description: packageJson?.description?.toString(), + }; + + const links: { title: string; url: string }[] = []; + + if (typeof packageJson.homepage === 'string') { + links.push({ + title: 'Homepage', + url: packageJson.homepage, + }); + } + + if ( + isJsonObject(packageJson.repository) && + typeof packageJson.repository?.url === 'string' + ) { + try { + const url = new URL(packageJson.repository?.url); + if (url.protocol === 'http:' || url.protocol === 'https:') { + // TODO(Rugvip): Support more variants + if ( + url.hostname === 'github.com' && + typeof packageJson.repository.directory === 'string' + ) { + const path = `${url.pathname}/tree/-/${packageJson.repository.directory}`; + url.pathname = path.replaceAll('//', '/'); + } + + links.push({ + title: 'Repository', + url: url.toString(), + }); + } + } catch { + /* ignored */ + } + } + + if (links.length > 0) { + info.links = links; + } + return info; +} + +function makeStringMatcher(pattern: string | undefined) { + if (!pattern) { + return () => true; + } + if (pattern.startsWith('/') && pattern.endsWith('/') && pattern.length > 2) { + const regex = new RegExp(pattern.slice(1, -1)); + return (str?: string) => (str ? regex.test(str) : false); + } + + return (str?: string) => str === pattern; +} + +function isJsonObject(value?: JsonValue): value is JsonObject { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} diff --git a/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx b/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx index 00551e16e3..7c6bb7958b 100644 --- a/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx +++ b/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx @@ -671,4 +671,123 @@ describe('createSpecializedApp', () => { expect(render(root).container.textContent).toBe('1-2-test-1-2'); }); + + describe('plugin info', () => { + const testExtension = createExtension({ + attachTo: { id: 'root', input: 'app' }, + output: [coreExtensionData.reactElement], + factory: () => [coreExtensionData.reactElement(
Test
)], + }); + + it('should throw unless accessed via an app', async () => { + const plugin = createFrontendPlugin({ + pluginId: 'test', + extensions: [testExtension], + }); + + const errorMsg = + "Attempted to load plugin info for plugin 'test', but the plugin instance is not installed in an app"; + await expect(plugin.info()).rejects.toThrow(errorMsg); + + const app = createSpecializedApp({ features: [plugin] }); + + await expect(plugin.info()).rejects.toThrow(errorMsg); + + const installedPlugin = app.tree.nodes.get('test')?.spec.source; + expect(installedPlugin).toBeDefined(); + const info = await installedPlugin?.info(); + expect(info).toEqual({}); + }); + + it('should forward plugin info', async () => { + const plugin = createFrontendPlugin({ + pluginId: 'test', + info: { + packageJson: () => import('../../package.json'), + }, + extensions: [testExtension], + }); + + const app = createSpecializedApp({ features: [plugin] }); + const info = await app.tree.nodes.get('test')?.spec.source?.info(); + expect(info).toMatchObject({ + packageName: '@backstage/frontend-app-api', + }); + }); + + it('should allow overriding plugin info per plugin', async () => { + const plugin = createFrontendPlugin({ + pluginId: 'test', + info: { + packageJson: () => import('../../package.json'), + }, + extensions: [testExtension], + }); + + const overriddenPlugin = plugin.withOverrides({ + extensions: [], + info: { + packageJson: () => Promise.resolve({ name: 'test-override' }), + }, + }); + + const app = createSpecializedApp({ features: [overriddenPlugin] }); + const info = await app.tree.nodes.get('test')?.spec.source?.info(); + expect(info).toMatchObject({ + packageName: 'test-override', + }); + }); + + it('should merge with plugin info from manifest', async () => { + const plugin = createFrontendPlugin({ + pluginId: 'test', + info: { + packageJson: () => import('../../package.json'), + manifest: async () => ({ + metadata: { + links: [{ title: 'Example', url: 'https://example.com' }], + }, + spec: { + owner: 'cubic-belugas', + }, + }), + }, + extensions: [testExtension], + }); + + const app = createSpecializedApp({ features: [plugin] }); + const info = await app.tree.nodes.get('test')?.spec.source?.info(); + expect(info).toEqual({ + packageName: '@backstage/frontend-app-api', + version: expect.any(String), + links: [{ title: 'Example', url: 'https://example.com' }], + ownerEntityRefs: ['group:default/cubic-belugas'], + }); + }); + + it('should allow overriding of the plugin info resolver', async () => { + const plugin = createFrontendPlugin({ + pluginId: 'test', + info: { + packageJson: () => import('../../package.json'), + }, + extensions: [testExtension], + }); + + const app = createSpecializedApp({ + features: [plugin], + async pluginInfoResolver(ctx) { + const { info } = await ctx.defaultResolver({ + packageJson: await ctx.packageJson(), + manifest: await ctx.manifest(), + }); + return { info: { packageName: `decorated:${info.packageName}` } }; + }, + }); + const info = await app.tree.nodes.get('test')?.spec.source?.info(); + expect(info).toEqual({ + packageName: 'decorated:@backstage/frontend-app-api', + }); + }); + }); }); diff --git a/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx b/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx index 5f6692d600..51b0f14250 100644 --- a/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx +++ b/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx @@ -31,6 +31,7 @@ import { routeResolutionApiRef, AppNode, ExtensionFactoryMiddleware, + FrontendFeature, } from '@backstage/frontend-plugin-api'; import { AnyApiFactory, @@ -74,8 +75,12 @@ import { ApiRegistry } from '../../../core-app-api/src/apis/system/ApiRegistry'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { AppIdentityProxy } from '../../../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy'; import { BackstageRouteObject } from '../routing/types'; -import { FrontendFeature, RouteInfo } from './types'; +import { RouteInfo } from './types'; import { matchRoutes } from 'react-router-dom'; +import { + createPluginInfoAttacher, + FrontendPluginInfoResolver, +} from './createPluginInfoAttacher'; function deduplicateFeatures( allFeatures: FrontendFeature[], @@ -209,9 +214,12 @@ export function createSpecializedApp(options?: { | ExtensionFactoryMiddleware | ExtensionFactoryMiddleware[]; flags?: { allowUnknownExtensionConfig?: boolean }; + pluginInfoResolver?: FrontendPluginInfoResolver; }): { apis: ApiHolder; tree: AppTree } { const config = options?.config ?? new ConfigReader({}, 'empty-config'); - const features = deduplicateFeatures(options?.features ?? []); + const features = deduplicateFeatures(options?.features ?? []).map( + createPluginInfoAttacher(config, options?.pluginInfoResolver), + ); const tree = resolveAppTree( 'root', diff --git a/packages/frontend-app-api/src/wiring/index.ts b/packages/frontend-app-api/src/wiring/index.ts index 4ecce3f184..fc52dcb7a1 100644 --- a/packages/frontend-app-api/src/wiring/index.ts +++ b/packages/frontend-app-api/src/wiring/index.ts @@ -15,4 +15,5 @@ */ export { createSpecializedApp } from './createSpecializedApp'; +export { type FrontendPluginInfoResolver } from './createPluginInfoAttacher'; export * from './types'; diff --git a/packages/frontend-defaults/CHANGELOG.md b/packages/frontend-defaults/CHANGELOG.md index ce49b98ee3..a255a1bff7 100644 --- a/packages/frontend-defaults/CHANGELOG.md +++ b/packages/frontend-defaults/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/frontend-defaults +## 0.2.3-next.0 + +### Patch Changes + +- fa5650c: Forwarded the new `pluginInfoResolver` option for `createApp`. +- Updated dependencies + - @backstage/plugin-app@0.1.10-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/frontend-app-api@0.11.3-next.0 + ## 0.2.2 ### Patch Changes diff --git a/packages/frontend-defaults/package.json b/packages/frontend-defaults/package.json index 30351c730b..a30accf222 100644 --- a/packages/frontend-defaults/package.json +++ b/packages/frontend-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-defaults", - "version": "0.2.2", + "version": "0.2.3-next.0", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-defaults/report.api.md b/packages/frontend-defaults/report.api.md index e9bcd99920..72f212b3b1 100644 --- a/packages/frontend-defaults/report.api.md +++ b/packages/frontend-defaults/report.api.md @@ -9,6 +9,7 @@ import { CreateAppRouteBinder } from '@backstage/frontend-app-api'; import { ExtensionFactoryMiddleware } from '@backstage/frontend-plugin-api'; import { FrontendFeature } from '@backstage/frontend-plugin-api'; import { FrontendFeatureLoader } from '@backstage/frontend-plugin-api'; +import { FrontendPluginInfoResolver } from '@backstage/frontend-app-api'; import { JSX as JSX_2 } from 'react'; import { ReactNode } from 'react'; @@ -44,6 +45,8 @@ export interface CreateAppOptions { | CreateAppFeatureLoader )[]; loadingComponent?: ReactNode; + // (undocumented) + pluginInfoResolver?: FrontendPluginInfoResolver; } // @public diff --git a/packages/frontend-defaults/src/createApp.test.tsx b/packages/frontend-defaults/src/createApp.test.tsx index 4fd1ba4c5b..4f627f1839 100644 --- a/packages/frontend-defaults/src/createApp.test.tsx +++ b/packages/frontend-defaults/src/createApp.test.tsx @@ -23,12 +23,15 @@ import { createFrontendPlugin, ThemeBlueprint, createFrontendModule, + useAppNode, + FrontendPluginInfo, } from '@backstage/frontend-plugin-api'; import { screen, waitFor } from '@testing-library/react'; import { CreateAppFeatureLoader, createApp } from './createApp'; import { mockApis, renderWithEffects } from '@backstage/test-utils'; import { featureFlagsApiRef, useApi } from '@backstage/core-plugin-api'; import { default as appPluginOriginal } from '@backstage/plugin-app'; +import { useState, useEffect } from 'react'; describe('createApp', () => { const appPlugin = appPluginOriginal.withOverrides({ @@ -119,6 +122,52 @@ describe('createApp', () => { ); }); + it('should allow overriding the plugin info resolver', async () => { + function TestComponent() { + const appNode = useAppNode(); + const [info, setInfo] = useState( + undefined, + ); + + useEffect(() => { + appNode?.spec.source?.info().then(setInfo); + }, [appNode]); + + return
Package name: {info?.packageName}
; + } + + const app = createApp({ + configLoader: async () => ({ config: mockApis.config() }), + features: [ + appPlugin, + createFrontendPlugin({ + pluginId: 'test', + extensions: [ + PageBlueprint.make({ + params: { + defaultPath: '/', + loader: async () => , + }, + }), + ], + }), + ], + pluginInfoResolver: async () => { + return { + info: { + packageName: '@test/test', + }, + }; + }, + }); + + await renderWithEffects(app.createRoot()); + + await expect( + screen.findByText('Package name: @test/test'), + ).resolves.toBeInTheDocument(); + }); + it('should support feature loaders', async () => { const loader: CreateAppFeatureLoader = { getLoaderName() { diff --git a/packages/frontend-defaults/src/createApp.tsx b/packages/frontend-defaults/src/createApp.tsx index f25b71ce18..e8baa3a764 100644 --- a/packages/frontend-defaults/src/createApp.tsx +++ b/packages/frontend-defaults/src/createApp.tsx @@ -30,6 +30,7 @@ import { ConfigReader } from '@backstage/config'; import { CreateAppRouteBinder, createSpecializedApp, + FrontendPluginInfoResolver, } from '@backstage/frontend-app-api'; import appPlugin from '@backstage/plugin-app'; import { discoverAvailableFeatures } from './discovery'; @@ -78,6 +79,7 @@ export interface CreateAppOptions { extensionFactoryMiddleware?: | ExtensionFactoryMiddleware | ExtensionFactoryMiddleware[]; + pluginInfoResolver?: FrontendPluginInfoResolver; } /** @@ -112,6 +114,7 @@ export function createApp(options?: CreateAppOptions): { features: [appPlugin, ...loadedFeatures], bindRoutes: options?.bindRoutes, extensionFactoryMiddleware: options?.extensionFactoryMiddleware, + pluginInfoResolver: options?.pluginInfoResolver, }); const rootEl = app.tree.root.instance!.getData( diff --git a/packages/frontend-dynamic-feature-loader/CHANGELOG.md b/packages/frontend-dynamic-feature-loader/CHANGELOG.md index ce0c8729f0..a93a11364c 100644 --- a/packages/frontend-dynamic-feature-loader/CHANGELOG.md +++ b/packages/frontend-dynamic-feature-loader/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/frontend-dynamic-feature-loader +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + ## 0.1.1 ### Patch Changes diff --git a/packages/frontend-dynamic-feature-loader/package.json b/packages/frontend-dynamic-feature-loader/package.json index e1e55da725..7c9a7c45bf 100644 --- a/packages/frontend-dynamic-feature-loader/package.json +++ b/packages/frontend-dynamic-feature-loader/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-dynamic-feature-loader", - "version": "0.1.1", + "version": "0.1.2-next.0", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-internal/CHANGELOG.md b/packages/frontend-internal/CHANGELOG.md index 2be23d529c..d3c097ed8b 100644 --- a/packages/frontend-internal/CHANGELOG.md +++ b/packages/frontend-internal/CHANGELOG.md @@ -1,5 +1,12 @@ # @internal/frontend +## 0.0.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + ## 0.0.9 ### Patch Changes diff --git a/packages/frontend-internal/package.json b/packages/frontend-internal/package.json index e11ea49e86..931cbc8eb3 100644 --- a/packages/frontend-internal/package.json +++ b/packages/frontend-internal/package.json @@ -1,6 +1,6 @@ { "name": "@internal/frontend", - "version": "0.0.9", + "version": "0.0.10-next.0", "backstage": { "role": "web-library", "inline": true diff --git a/packages/frontend-internal/src/wiring/InternalFrontendPlugin.ts b/packages/frontend-internal/src/wiring/InternalFrontendPlugin.ts index a7be9614b6..af69370f76 100644 --- a/packages/frontend-internal/src/wiring/InternalFrontendPlugin.ts +++ b/packages/frontend-internal/src/wiring/InternalFrontendPlugin.ts @@ -19,6 +19,7 @@ import { FeatureFlagConfig, FrontendPlugin, } from '@backstage/frontend-plugin-api'; +import { JsonObject } from '@backstage/types'; import { OpaqueType } from '@internal/opaque'; export const OpaqueFrontendPlugin = OpaqueType.create<{ @@ -27,6 +28,10 @@ export const OpaqueFrontendPlugin = OpaqueType.create<{ readonly version: 'v1'; readonly extensions: Extension[]; readonly featureFlags: FeatureFlagConfig[]; + readonly infoOptions?: { + packageJson?: () => Promise; + manifest?: () => Promise; + }; }; }>({ type: '@backstage/FrontendPlugin', diff --git a/packages/frontend-plugin-api/CHANGELOG.md b/packages/frontend-plugin-api/CHANGELOG.md index 148389e1a1..4bad433789 100644 --- a/packages/frontend-plugin-api/CHANGELOG.md +++ b/packages/frontend-plugin-api/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/frontend-plugin-api +## 0.10.3-next.0 + +### Patch Changes + +- 9e3868f: Added a new optional `info` option to `createFrontendPlugin` that lets you provide a loaders for different sources of metadata information about the plugin. + + There are two available loaders. The first one is `info.packageJson`, which can be used to point to a `package.json` file for the plugin. This is recommended for any plugin that is defined within its own package, especially all plugins that are published to a package registry. Typical usage looks like this: + + ```ts + export default createFrontendPlugin({ + pluginId: '...', + info: { + packageJson: () => import('../package.json'), + }, + }); + ``` + + The second loader is `info.manifest`, which can be used to point to an opaque plugin manifest. This **MUST ONLY** be used by plugins that are intended for use within a single organization. Plugins that are published to an open package registry should **NOT** use this loader. The loader is useful for adding additional internal metadata associated with the plugin, and it is up to the Backstage app to decide how these manifests are parsed and used. The default manifest parser in an app created with `createApp` from `@backstage/frontend-defaults` is able to parse the default `catalog-info.yaml` format and built-in fields such as `spec.owner`. + + Typical usage looks like this: + + ```ts + export default createFrontendPlugin({ + pluginId: '...', + info: { + manifest: () => import('../catalog-info.yaml'), + }, + }); + ``` + +- 6f48f71: Added a new `useAppNode` hook, which can be used to get a reference to the `AppNode` from by the closest `ExtensionBoundary`. + ## 0.10.2 ### Patch Changes diff --git a/packages/frontend-plugin-api/package.json b/packages/frontend-plugin-api/package.json index f68db29446..bdfd60c39d 100644 --- a/packages/frontend-plugin-api/package.json +++ b/packages/frontend-plugin-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-plugin-api", - "version": "0.10.2", + "version": "0.10.3-next.0", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-plugin-api/report.api.md b/packages/frontend-plugin-api/report.api.md index 1c01e0ac83..9ac89d8331 100644 --- a/packages/frontend-plugin-api/report.api.md +++ b/packages/frontend-plugin-api/report.api.md @@ -1324,14 +1324,38 @@ export interface FrontendPlugin< getExtension(id: TId): TExtensionMap[TId]; // (undocumented) readonly id: string; + info(): Promise; // (undocumented) readonly routes: TRoutes; // (undocumented) withOverrides(options: { extensions: Array; + info?: FrontendPluginInfoOptions; }): FrontendPlugin; } +// @public +export interface FrontendPluginInfo { + description?: string; + links?: Array<{ + title: string; + url: string; + }>; + ownerEntityRefs?: string[]; + packageName?: string; + version?: string; +} + +// @public +export type FrontendPluginInfoOptions = { + packageJson?: () => Promise< + { + name: string; + } & JsonObject + >; + manifest?: () => Promise; +}; + export { githubAuthApiRef }; export { gitlabAuthApiRef }; @@ -1516,6 +1540,8 @@ export interface PluginOptions< // (undocumented) featureFlags?: FeatureFlagConfig[]; // (undocumented) + info?: FrontendPluginInfoOptions; + // (undocumented) pluginId: TId; // (undocumented) routes?: TRoutes; @@ -1806,6 +1832,9 @@ export { useApi }; export { useApiHolder }; +// @public +export function useAppNode(): AppNode | undefined; + // @public export function useComponentRef( ref: ComponentRef, diff --git a/packages/frontend-plugin-api/src/components/AppNodeProvider.test.tsx b/packages/frontend-plugin-api/src/components/AppNodeProvider.test.tsx new file mode 100644 index 0000000000..fcc3b74c59 --- /dev/null +++ b/packages/frontend-plugin-api/src/components/AppNodeProvider.test.tsx @@ -0,0 +1,84 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + createVersionedContext, + createVersionedValueMap, +} from '@backstage/version-bridge'; +import { AppNode } from '../apis'; +import { renderHook } from '@testing-library/react'; +import { AppNodeProvider, useAppNode } from './AppNodeProvider'; +import { withLogCollector } from '@backstage/test-utils'; + +describe('AppNodeProvider', () => { + it('should provide app node context to children', () => { + const node = { id: 'test' } as unknown as AppNode; + const { result } = renderHook(() => useAppNode(), { + wrapper: ({ children }) => ( + {children} + ), + }); + + expect(result.current).toBe(node); + }); + + it('should return undefined when used outside provider', () => { + const { result } = renderHook(() => useAppNode()); + expect(result.current).toBeUndefined(); + }); + + it('should return the closest app node', () => { + const node1 = { id: 'test1' } as unknown as AppNode; + const node2 = { id: 'test2' } as unknown as AppNode; + + const { result } = renderHook(() => useAppNode(), { + wrapper: ({ children }) => ( + + {children} + + ), + }); + + expect(result.current).toBe(node2); + }); + + it('should throw error for invalid context version', () => { + const node = { id: 'test' } as unknown as AppNode; + const Context = createVersionedContext('app-node-context'); + const value = createVersionedValueMap({ 2: { node } }); + + const { error } = withLogCollector(() => { + expect(() => + renderHook(() => useAppNode(), { + wrapper: ({ children }) => ( + {children} + ), + }), + ).toThrow('AppNodeContext v1 not available'); + }); + expect(error).toEqual([ + expect.objectContaining({ + detail: new Error('AppNodeContext v1 not available'), + }), + expect.objectContaining({ + detail: new Error('AppNodeContext v1 not available'), + }), + expect.stringContaining( + 'The above error occurred in the component:', + ), + ]); + }); +}); diff --git a/packages/frontend-plugin-api/src/components/AppNodeProvider.tsx b/packages/frontend-plugin-api/src/components/AppNodeProvider.tsx new file mode 100644 index 0000000000..2bc9715c10 --- /dev/null +++ b/packages/frontend-plugin-api/src/components/AppNodeProvider.tsx @@ -0,0 +1,74 @@ +/* + * 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 { + createVersionedContext, + createVersionedValueMap, + useVersionedContext, +} from '@backstage/version-bridge'; +import { AppNode } from '../apis'; +import { ReactNode } from 'react'; + +const CONTEXT_KEY = 'app-node-context'; + +type AppNodeContextV1 = { + node?: AppNode; +}; + +type AppNodeContextMap = { + 1: AppNodeContextV1; +}; + +const AppNodeContext = createVersionedContext(CONTEXT_KEY); + +/** @internal */ +export function AppNodeProvider({ + node, + children, +}: { + node: AppNode; + children: ReactNode; +}) { + const versionedValue = createVersionedValueMap({ 1: { node } }); + + return ; +} + +/** + * React hook providing access to the current {@link AppNode}. + * + * @public + * @remarks + * + * This hook will return the {@link AppNode} for the closest extension. This + * relies on the extension using the {@link (ExtensionBoundary:function)} component in its + * implementation, which is included by default for all common blueprints. + * + * If the current component is not inside an {@link (ExtensionBoundary:function)}, it will + * return `undefined`. + */ +export function useAppNode(): AppNode | undefined { + const versionedContext = useVersionedContext(CONTEXT_KEY); + if (!versionedContext) { + return undefined; + } + + const context = versionedContext.atVersion(1); + if (!context) { + throw new Error('AppNodeContext v1 not available'); + } + return context.node; +} diff --git a/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx b/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx index a55afeeffd..7f14b992de 100644 --- a/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx +++ b/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx @@ -28,6 +28,7 @@ import { routableExtensionRenderedEvent } from '../../../core-plugin-api/src/ana import { AppNode, useComponentRef } from '../apis'; import { coreComponentRefs } from './coreComponentRefs'; import { coreExtensionData } from '../wiring'; +import { AppNodeProvider } from './AppNodeProvider'; type RouteTrackerProps = PropsWithChildren<{ disableTracking?: boolean; @@ -80,15 +81,17 @@ export function ExtensionBoundary(props: ExtensionBoundaryProps) { }; return ( - }> - - - - {children} - - - - + + }> + + + + {children} + + + + + ); } diff --git a/packages/frontend-plugin-api/src/components/index.ts b/packages/frontend-plugin-api/src/components/index.ts index 5066144000..31a53be24c 100644 --- a/packages/frontend-plugin-api/src/components/index.ts +++ b/packages/frontend-plugin-api/src/components/index.ts @@ -20,3 +20,4 @@ export { } from './ExtensionBoundary'; export { coreComponentRefs } from './coreComponentRefs'; export { createComponentRef, type ComponentRef } from './createComponentRef'; +export { useAppNode } from './AppNodeProvider'; diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts index 359f9da417..c97c60e89e 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts @@ -281,6 +281,60 @@ describe('createFrontendPlugin', () => { ).toThrow("Plugin 'test' provided duplicate extensions: test/2, test/3"); }); + describe('info', () => { + it('should support reading info from package.json', async () => { + const plugin = createFrontendPlugin({ + pluginId: 'test', + info: { packageJson: () => Promise.resolve({ name: '@test/test' }) }, + }); + + await expect((plugin as any).infoOptions?.packageJson()).resolves.toEqual( + { name: '@test/test' }, + ); + }); + + it('should support reading info from actual package.json', async () => { + const plugin = createFrontendPlugin({ + pluginId: 'test', + info: { packageJson: () => import('../../package.json') }, + }); + + await expect( + (plugin as any).infoOptions?.packageJson(), + ).resolves.toMatchObject({ name: '@backstage/frontend-plugin-api' }); + }); + + it('should support reading info from opaque manifest', async () => { + const plugin = createFrontendPlugin({ + pluginId: 'test', + info: { manifest: () => Promise.resolve({ owner: 'me' }) }, + }); + + await expect((plugin as any).infoOptions?.manifest()).resolves.toEqual({ + owner: 'me', + }); + }); + + it('should throw when trying to load info without installing in an app', async () => { + await expect( + createFrontendPlugin({ + pluginId: 'test', + }).info(), + ).rejects.toThrow( + "Attempted to load plugin info for plugin 'test', but the plugin instance is not installed in an app", + ); + + await expect( + createFrontendPlugin({ + pluginId: 'test', + info: { packageJson: () => Promise.resolve({ name: '@test/test' }) }, + }).info(), + ).rejects.toThrow( + "Attempted to load plugin info for plugin 'test', but the plugin instance is not installed in an app", + ); + }); + }); + describe('overrides', () => { it('should return a plugin instance with the correct namespace', () => { const plugin = createFrontendPlugin({ diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts index b7c530a0c0..c16ac329cc 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts @@ -25,6 +25,67 @@ import { } from './resolveExtensionDefinition'; import { AnyExternalRoutes, AnyRoutes, FeatureFlagConfig } from './types'; import { MakeSortedExtensionsMap } from './MakeSortedExtensionsMap'; +import { JsonObject } from '@backstage/types'; + +/** + * Information about the plugin. + * + * @public + * @remarks + * + * This interface is intended to be extended via [module + * augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation) + * in order to add fields that are specific to each project. + * + * For example, one might add a `slackChannel` field that is read from the + * opaque manifest file. + * + * See the options for `createApp` for more information about how to + * customize the parsing of manifest files. + */ +export interface FrontendPluginInfo { + /** + * The name of the package that implements the plugin. + */ + packageName?: string; + + /** + * The version of the plugin, typically the version of the package.json file. + */ + version?: string; + + /** + * As short description of the plugin, typically the description field in + * package.json. + */ + description?: string; + + /** + * The owner entity references of the plugin. + */ + ownerEntityRefs?: string[]; + + /** + * Links related to the plugin. + */ + links?: Array<{ title: string; url: string }>; +} + +/** + * Options for providing information for a plugin. + * + * @public + */ +export type FrontendPluginInfoOptions = { + /** + * A loader function for the package.json file for the plugin. + */ + packageJson?: () => Promise<{ name: string } & JsonObject>; + /** + * A loader function for an opaque manifest file for the plugin. + */ + manifest?: () => Promise; +}; /** @public */ export interface FrontendPlugin< @@ -38,9 +99,19 @@ export interface FrontendPlugin< readonly id: string; readonly routes: TRoutes; readonly externalRoutes: TExternalRoutes; + + /** + * Loads the plugin info. + */ + info(): Promise; getExtension(id: TId): TExtensionMap[TId]; withOverrides(options: { extensions: Array; + + /** + * Overrides the original info loaders of the plugin one by one. + */ + info?: FrontendPluginInfoOptions; }): FrontendPlugin; } @@ -56,6 +127,7 @@ export interface PluginOptions< externalRoutes?: TExternalRoutes; extensions?: TExtensions; featureFlags?: FeatureFlagConfig[]; + info?: FrontendPluginInfoOptions; } /** @public */ @@ -150,6 +222,14 @@ export function createFrontendPlugin< externalRoutes: options.externalRoutes ?? ({} as TExternalRoutes), featureFlags: options.featureFlags ?? [], extensions: extensions, + infoOptions: options.info, + + // This method is overridden when the plugin instance is installed in an app + async info() { + throw new Error( + `Attempted to load plugin info for plugin '${pluginId}', but the plugin instance is not installed in an app`, + ); + }, getExtension(id) { const ext = extensionDefinitionsById.get(id); if (!ext) { @@ -178,6 +258,10 @@ export function createFrontendPlugin< ...options, pluginId, extensions: [...nonOverriddenExtensions, ...overrides.extensions], + info: { + ...options.info, + ...overrides.info, + }, }); }, }); diff --git a/packages/frontend-plugin-api/src/wiring/index.ts b/packages/frontend-plugin-api/src/wiring/index.ts index b584fc9769..24a4e1653f 100644 --- a/packages/frontend-plugin-api/src/wiring/index.ts +++ b/packages/frontend-plugin-api/src/wiring/index.ts @@ -40,6 +40,8 @@ export { createFrontendPlugin, type FrontendPlugin, type PluginOptions, + type FrontendPluginInfo, + type FrontendPluginInfoOptions, } from './createFrontendPlugin'; export { createFrontendModule, diff --git a/packages/frontend-test-utils/CHANGELOG.md b/packages/frontend-test-utils/CHANGELOG.md index af929610f5..1bc3a595b6 100644 --- a/packages/frontend-test-utils/CHANGELOG.md +++ b/packages/frontend-test-utils/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/frontend-test-utils +## 0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.1.10-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/frontend-app-api@0.11.3-next.0 + ## 0.3.2 ### Patch Changes diff --git a/packages/frontend-test-utils/package.json b/packages/frontend-test-utils/package.json index 8e60e5ff69..607260ed01 100644 --- a/packages/frontend-test-utils/package.json +++ b/packages/frontend-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-test-utils", - "version": "0.3.2", + "version": "0.3.3-next.0", "backstage": { "role": "web-library" }, diff --git a/packages/repo-tools/CHANGELOG.md b/packages/repo-tools/CHANGELOG.md index d6db1ed688..3ca8db071e 100644 --- a/packages/repo-tools/CHANGELOG.md +++ b/packages/repo-tools/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/repo-tools +## 0.14.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/cli-common@0.1.15 + - @backstage/cli-node@0.2.13 + - @backstage/config-loader@1.10.1 + - @backstage/errors@1.2.7 + +## 0.14.0-next.0 + +### Minor Changes + +- bf9a173: Add support for caching the per-package output from the `package-docs` command. + +### Patch Changes + +- 2d20024: Fix an issue where errors were not printed to console when running `backstage-repo-tools schema openapi generate` without the `--watch` flag. +- e643ee4: Add missing highlight language for the `package-docs` command. +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/cli-node@0.2.13 + - @backstage/config-loader@1.10.1 + ## 0.13.3 ### Patch Changes diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index de7683e39b..1d266dcb9b 100644 --- a/packages/repo-tools/package.json +++ b/packages/repo-tools/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/repo-tools", - "version": "0.13.3", + "version": "0.14.0-next.1", "description": "CLI for Backstage repo tooling ", "backstage": { "role": "cli" @@ -69,6 +69,7 @@ "commander": "^12.0.0", "fs-extra": "^11.2.0", "glob": "^8.0.3", + "globby": "^11.0.0", "is-glob": "^4.0.3", "js-yaml": "^4.1.0", "just-diff": "^6.0.2", @@ -81,7 +82,8 @@ "portfinder": "^1.0.32", "tar": "^6.1.12", "ts-morph": "^24.0.0", - "yaml-diff-patch": "^2.0.0" + "yaml-diff-patch": "^2.0.0", + "zod": "^3.22.4" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/packages/repo-tools/src/commands/package-docs/Cache.test.ts b/packages/repo-tools/src/commands/package-docs/Cache.test.ts new file mode 100644 index 0000000000..3fe52dcabd --- /dev/null +++ b/packages/repo-tools/src/commands/package-docs/Cache.test.ts @@ -0,0 +1,178 @@ +/* + * 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 { Lockfile } from '@backstage/cli-node'; +import { PackageDocsCache } from './Cache'; +import { + createMockDirectory, + MockDirectory, +} from '@backstage/backend-test-utils'; +import { readFile } from 'fs/promises'; +import { join as joinPath } from 'path'; + +jest.mock('crypto', () => { + const hash = { + update: jest.fn(), + digest: jest.fn().mockReturnValue('test'), + }; + return { + createHash: jest.fn().mockReturnValue(hash), + }; +}); + +describe('PackageDocsCache', () => { + let testDir: MockDirectory; + beforeAll(async () => { + testDir = createMockDirectory(); + }); + afterEach(async () => { + testDir.clear(); + }); + it('should be able to parse cache files', async () => { + testDir.addContent({ + '.cache': { + 'package-docs': { + test: { + 'cache.json': JSON.stringify({ + hash: 'test', + packageName: 'test', + restoreTo: 'test', + version: '1', + }), + }, + }, + }, + test: { + 'package.json': JSON.stringify({ + name: '@test/test', + }), + }, + }); + const lockfile = { + getDependencyTreeHash: () => 'test', + } as any as Lockfile; + const cache = await PackageDocsCache.loadAsync(testDir.path, lockfile); + expect(await cache.has('test')).toBe(true); + }); + + it('should be able to restore cache', async () => { + testDir.addContent({ + '.cache': { + 'package-docs': { + test: { + 'cache.json': JSON.stringify({ + hash: 'test', + packageName: 'test', + restoreTo: 'test', + version: '1', + }), + contents: { + 'src/index.ts': 'export const test = "test";', + }, + }, + }, + }, + test: { + 'package.json': JSON.stringify({ + name: '@test/test', + }), + }, + }); + const lockfile = { + getDependencyTreeHash: () => 'test', + } as any as Lockfile; + const cache = await PackageDocsCache.loadAsync(testDir.path, lockfile); + await cache.restore('test'); + expect( + await readFile(joinPath(testDir.path, 'test', 'src/index.ts'), 'utf-8'), + ).toBe('export const test = "test";'); + }); + + it('should be able to write cache', async () => { + testDir.addContent({ + '.cache': {}, + test: { + 'package.json': JSON.stringify({ + name: '@test/test', + }), + 'src/index.ts': 'export const test = "test";', + }, + }); + const lockfile = { + getDependencyTreeHash: () => 'test', + } as any as Lockfile; + const cache = await PackageDocsCache.loadAsync(testDir.path, lockfile); + await cache.write('test', joinPath(testDir.path, 'test')); + expect( + await readFile( + joinPath(testDir.path, '.cache', 'package-docs', 'test', 'cache.json'), + 'utf-8', + ), + ).toBe( + JSON.stringify({ + hash: 'test', + packageName: '@test/test', + restoreTo: 'test', + version: '1', + }), + ); + expect( + await readFile( + joinPath( + testDir.path, + '.cache', + 'package-docs', + 'test', + 'contents', + 'src/index.ts', + ), + 'utf-8', + ), + ).toBe('export const test = "test";'); + }); + + it.each([ + { + content: JSON.stringify({ + hash: 'test', + packageName: 'test', + restoreTo: 'test', + version: '2', + }), + }, + { + content: JSON.stringify({ + hash: 'test', + packageName: 1, + }), + }, + ])('should skip invalid cache files', async content => { + testDir.addContent({ + '.cache': {}, + test: { + 'package.json': JSON.stringify({ + name: '@test/test', + }), + }, + 'cache.json': content, + }); + const lockfile = { + getDependencyTreeHash: () => 'test', + } as any as Lockfile; + const cache = await PackageDocsCache.loadAsync(testDir.path, lockfile); + expect(await cache.has('test')).toBe(false); + }); +}); diff --git a/packages/repo-tools/src/commands/package-docs/Cache.ts b/packages/repo-tools/src/commands/package-docs/Cache.ts new file mode 100644 index 0000000000..487db81347 --- /dev/null +++ b/packages/repo-tools/src/commands/package-docs/Cache.ts @@ -0,0 +1,158 @@ +/* + * 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 { readFile, writeFile, cp } from 'fs/promises'; +import globby from 'globby'; +import { dirname, join as joinPath, relative } from 'path'; +import crypto from 'crypto'; +import { Lockfile } from '@backstage/cli-node'; +import { exists, rm, mkdirp } from 'fs-extra'; +import { z } from 'zod'; +import { CACHE_DIR, CACHE_FILE } from './constants'; + +const version = '1'; + +interface CacheEntry { + hash: string; + packageName: string; + restoreTo: string; + version: string; +} + +const cacheEntrySchema = z.object({ + hash: z.string(), + packageName: z.string(), + restoreTo: z.string(), + version: z.string(), +}); + +export class PackageDocsCache { + // A map of package directory to package hash. + private keyCache: Map; + constructor( + private readonly lockfile: Lockfile, + // A map of package directory to cache entry. + private readonly cache: Map, + private readonly baseDirectory: string, + ) { + this.keyCache = new Map(); + } + static async loadAsync(baseDirectory: string, lockfile: Lockfile) { + const cacheDir = joinPath(baseDirectory, CACHE_DIR); + await mkdirp(cacheDir); + const cacheFiles = await globby(`**/${CACHE_FILE}`, { + cwd: cacheDir, + }); + const map = new Map(); + for (const file of cacheFiles) { + const pkg = dirname(file); + const cache = await readFile(joinPath(cacheDir, file), 'utf-8'); + try { + const cacheJson = JSON.parse(cache); + const parsed = cacheEntrySchema.parse(cacheJson); + if (parsed.version !== version) { + console.warn( + `Skipping cache file ${file} due to version mismatch: ${parsed.version} !== ${version}`, + ); + continue; + } + map.set(pkg, parsed); + } catch (e) { + console.error(`Skipping unparseable cache file ${file}: ${e}`); + } + } + return new PackageDocsCache(lockfile, map, baseDirectory); + } + + async directoryToName(directory: string) { + const packageJson = await readFile( + joinPath(this.baseDirectory, directory, 'package.json'), + 'utf-8', + ); + return JSON.parse(packageJson).name; + } + + private async toKey(pkg: string) { + if (this.keyCache.has(pkg)) { + return this.keyCache.get(pkg)!; + } + const name = await this.directoryToName(pkg); + const result = await globby('src/**', { + gitignore: true, + onlyFiles: true, + cwd: pkg, + }); + + const hash = crypto.createHash('sha1'); + hash.update(version); + hash.update('\0'); + + for (const path of result.sort()) { + const absPath = joinPath(this.baseDirectory, pkg, path); + const pathInPackage = joinPath(absPath, path); + hash.update(pathInPackage); + hash.update('\0'); + hash.update(await readFile(absPath)); + hash.update('\0'); + } + hash.update(this.lockfile.getDependencyTreeHash(name)); + hash.update('\0'); + const hashString = hash.digest('hex'); + this.keyCache.set(pkg, hashString); + return hashString; + } + + async has(pkg: string) { + const cache = this.cache.get(pkg); + if (!cache) { + return false; + } + const hashString = await this.toKey(pkg); + return cache.hash === hashString; + } + + async restore(pkg: string) { + if (!this.has(pkg)) { + throw new Error(`Cache entry for ${pkg} not found`); + } + const cacheEntry = this.cache.get(pkg); + const restoreTo = cacheEntry!.restoreTo; + const cacheDir = joinPath(this.baseDirectory, CACHE_DIR, pkg); + const contentsDir = joinPath(cacheDir, 'contents'); + + const targetDir = joinPath(this.baseDirectory, restoreTo); + await mkdirp(targetDir); + await cp(contentsDir, targetDir, { recursive: true }); + } + + async write(pkg: string, contentDirectory: string) { + const cacheDir = joinPath(this.baseDirectory, CACHE_DIR, pkg); + const contentsDir = joinPath(cacheDir, 'contents'); + if (await exists(contentsDir)) { + await rm(contentsDir, { recursive: true }); + } else { + await mkdirp(contentsDir); + } + const hashString = await this.toKey(pkg); + await cp(contentDirectory, contentsDir, { recursive: true }); + const cacheEntry: CacheEntry = { + hash: hashString, + packageName: await this.directoryToName(pkg), + restoreTo: relative(this.baseDirectory, contentDirectory), + version, + }; + await writeFile(joinPath(cacheDir, CACHE_FILE), JSON.stringify(cacheEntry)); + } +} diff --git a/packages/repo-tools/src/commands/package-docs/command.ts b/packages/repo-tools/src/commands/package-docs/command.ts index 8c30b06145..aa5edc7651 100644 --- a/packages/repo-tools/src/commands/package-docs/command.ts +++ b/packages/repo-tools/src/commands/package-docs/command.ts @@ -17,8 +17,11 @@ import { exec } from 'child_process'; import { promisify } from 'util'; import { paths as cliPaths, resolvePackagePaths } from '../../lib/paths'; import { createTemporaryTsConfig } from './utils'; -import { mkdir, readFile, writeFile } from 'fs/promises'; +import { readFile, writeFile } from 'fs/promises'; import pLimit from 'p-limit'; +import { mkdirp } from 'fs-extra'; +import { PackageDocsCache } from './Cache'; +import { Lockfile } from '@backstage/cli-node'; const limit = pLimit(8); @@ -38,6 +41,7 @@ const EXCLUDE = [ 'packages/techdocs-cli-embedded-app', 'packages/yarn-plugin', 'packages/backend', + 'packages/backend-legacy', ]; const HIGHLIGHT_LANGUAGES = [ @@ -52,6 +56,7 @@ const HIGHLIGHT_LANGUAGES = [ 'diff', 'js', 'json', + 'docker', ]; function getExports(packageJson: any) { @@ -75,41 +80,35 @@ async function generateDocJson(pkg: string) { !exports.length || !exports.some(e => e.startsWith('src') || e.startsWith('./src')) ) { - return; + return false; } - try { - await mkdir(cliPaths.resolveTargetRoot(`dist-types`, pkg), { - recursive: true, - }); + await mkdirp(cliPaths.resolveTargetRoot(`dist-types`, pkg)); - const { stdout, stderr } = await execAsync( - [ - cliPaths.resolveTargetRoot('node_modules/.bin/typedoc'), - '--json', - cliPaths.resolveTargetRoot(`dist-types`, pkg, 'docs.json'), - '--tsconfig', - temporaryTsConfigPath, - '--basePath', - cliPaths.targetRoot, - '--skipErrorChecking', - ...(getExports(packageJson).flatMap(e => [ - '--entryPoints', - e, - ]) as string[]), - ].join(' '), - { - cwd: pkg, - env: { ...process.env, NODE_OPTIONS: '--max-old-space-size=12288' }, - }, - ); - console.log(`### Processed ${pkg}`); - console.log(stdout); - console.error(stderr); - } catch (e) { - console.error('Failed to generate docs for', pkg); - console.error(e); - } + const { stdout, stderr } = await execAsync( + [ + cliPaths.resolveTargetRoot('node_modules/.bin/typedoc'), + '--json', + cliPaths.resolveTargetRoot(`dist-types`, pkg, 'docs.json'), + '--tsconfig', + temporaryTsConfigPath, + '--basePath', + cliPaths.targetRoot, + '--skipErrorChecking', + ...(getExports(packageJson).flatMap(e => [ + '--entryPoints', + e, + ]) as string[]), + ].join(' '), + { + cwd: pkg, + env: { ...process.env, NODE_OPTIONS: '--max-old-space-size=12288' }, + }, + ); + console.log(`### Processed ${pkg}`); + console.log(stdout); + console.error(stderr); + return true; } export default async function packageDocs(paths: string[] = [], opts: any) { @@ -120,6 +119,11 @@ export default async function packageDocs(paths: string[] = [], opts: any) { exclude: opts.exclude, }); + const cache = await PackageDocsCache.loadAsync( + cliPaths.resolveTargetRoot(), + await Lockfile.load(cliPaths.resolveTargetRoot('yarn.lock')), + ); + console.log(`### Generating docs.`); await Promise.all( selectedPackageDirs.map(pkg => @@ -127,8 +131,29 @@ export default async function packageDocs(paths: string[] = [], opts: any) { if (EXCLUDE.includes(pkg)) { return; } - console.log(`### Processing ${pkg}`); - await generateDocJson(pkg); + if (await cache.has(pkg)) { + console.log(`### Skipping ${pkg} due to cache hit`); + try { + await cache.restore(pkg); + return; + } catch (e) { + console.error('Failed to restore cache for', pkg); + console.error(e); + } + } + try { + console.log(`### Processing ${pkg}`); + const success = await generateDocJson(pkg); + if (success) { + await cache.write( + pkg, + cliPaths.resolveTargetRoot(`dist-types`, pkg), + ); + } + } catch (e) { + console.error('Failed to generate docs for', pkg); + console.error(e); + } }), ), ); diff --git a/plugins/scaffolder-backend/src/lib/index.ts b/packages/repo-tools/src/commands/package-docs/constants.ts similarity index 81% rename from plugins/scaffolder-backend/src/lib/index.ts rename to packages/repo-tools/src/commands/package-docs/constants.ts index 20096f9c03..a8372afad8 100644 --- a/plugins/scaffolder-backend/src/lib/index.ts +++ b/packages/repo-tools/src/commands/package-docs/constants.ts @@ -1,5 +1,5 @@ /* - * Copyright 2022 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,4 +14,5 @@ * limitations under the License. */ -export * from './templating'; +export const CACHE_FILE = 'cache.json'; +export const CACHE_DIR = '.cache/package-docs'; diff --git a/packages/repo-tools/templates/typescript-backstage-client/modelEnum.mustache b/packages/repo-tools/templates/typescript-backstage-client/modelEnum.mustache index 3a1b5ce86d..2461d8c549 100644 --- a/packages/repo-tools/templates/typescript-backstage-client/modelEnum.mustache +++ b/packages/repo-tools/templates/typescript-backstage-client/modelEnum.mustache @@ -18,14 +18,3 @@ export enum {{classname}} { */ export type {{classname}} = {{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}} | {{/-last}}{{/enumVars}}{{/allowableValues}}; -/** - * @public - */ -export const {{classname}} = { -{{#allowableValues}} -{{#enumVars}} - {{name}}: {{{value}}} as {{classname}}{{^-last}},{{/-last}} -{{/enumVars}} -{{/allowableValues}} -}; -{{/stringEnums}} diff --git a/packages/repo-tools/templates/typescript-backstage-server/modelEnum.mustache b/packages/repo-tools/templates/typescript-backstage-server/modelEnum.mustache index 3a1b5ce86d..2461d8c549 100644 --- a/packages/repo-tools/templates/typescript-backstage-server/modelEnum.mustache +++ b/packages/repo-tools/templates/typescript-backstage-server/modelEnum.mustache @@ -18,14 +18,3 @@ export enum {{classname}} { */ export type {{classname}} = {{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}} | {{/-last}}{{/enumVars}}{{/allowableValues}}; -/** - * @public - */ -export const {{classname}} = { -{{#allowableValues}} -{{#enumVars}} - {{name}}: {{{value}}} as {{classname}}{{^-last}},{{/-last}} -{{/enumVars}} -{{/allowableValues}} -}; -{{/stringEnums}} diff --git a/packages/scaffolder-internal/CHANGELOG.md b/packages/scaffolder-internal/CHANGELOG.md index 10b798a4d6..d397fdc00e 100644 --- a/packages/scaffolder-internal/CHANGELOG.md +++ b/packages/scaffolder-internal/CHANGELOG.md @@ -1,5 +1,21 @@ # @internal/scaffolder +## 0.0.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.16.1-next.1 + - @backstage/frontend-plugin-api@0.10.3-next.0 + +## 0.0.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/plugin-scaffolder-react@1.16.1-next.0 + ## 0.0.9 ### Patch Changes diff --git a/packages/scaffolder-internal/package.json b/packages/scaffolder-internal/package.json index 7e9ce86c46..d9e9bb54cf 100644 --- a/packages/scaffolder-internal/package.json +++ b/packages/scaffolder-internal/package.json @@ -1,6 +1,6 @@ { "name": "@internal/scaffolder", - "version": "0.0.9", + "version": "0.0.10-next.1", "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 d706d0ed77..53cabb42bb 100644 --- a/packages/techdocs-cli-embedded-app/CHANGELOG.md +++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md @@ -1,5 +1,35 @@ # techdocs-cli-embedded-app +## 0.2.109-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.32.2-next.0 + - @backstage/plugin-techdocs@1.13.0-next.1 + - @backstage/plugin-catalog@1.31.0-next.1 + - @backstage/app-defaults@1.6.2 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.0 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/integration-react@1.2.7 + - @backstage/test-utils@1.7.8 + - @backstage/theme@0.6.6 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + +## 0.2.109-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.13.0-next.0 + - @backstage/plugin-catalog@1.31.0-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + - @backstage/cli@0.32.1 + - @backstage/integration-react@1.2.7 + ## 0.2.108 ### Patch Changes diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index d4afe65fbb..71dc5b6bc2 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.108", + "version": "0.2.109-next.1", "backstage": { "role": "frontend" }, diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md index dfd1668143..26179b1e80 100644 --- a/packages/techdocs-cli/CHANGELOG.md +++ b/packages/techdocs-cli/CHANGELOG.md @@ -1,5 +1,24 @@ # @techdocs/cli +## 1.9.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/plugin-techdocs-node@1.13.4-next.1 + +## 1.9.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.1-next.0 + - @backstage/plugin-techdocs-node@1.13.4-next.0 + ## 1.9.3 ### Patch Changes diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index 642b073b9c..96ff8377ad 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -1,6 +1,6 @@ { "name": "@techdocs/cli", - "version": "1.9.3", + "version": "1.9.4-next.1", "description": "Utility CLI for managing TechDocs sites in Backstage.", "backstage": { "role": "cli" diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index fac9919159..960b46abf2 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-api-docs +## 0.12.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.31.0-next.1 + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-react@0.4.34 + +## 0.12.8-next.0 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/plugin-catalog@1.31.0-next.0 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + ## 0.12.7 ### Patch Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index b9f5b1a00f..ade2c0530b 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.7", + "version": "0.12.8-next.1", "description": "A Backstage plugin that helps represent API entities in the frontend", "backstage": { "role": "frontend-plugin", diff --git a/plugins/api-docs/src/alpha.tsx b/plugins/api-docs/src/alpha.tsx index cb55f620a7..356328a61d 100644 --- a/plugins/api-docs/src/alpha.tsx +++ b/plugins/api-docs/src/alpha.tsx @@ -228,6 +228,7 @@ const apiDocsApisEntityContent = EntityContentBlueprint.make({ export default createFrontendPlugin({ pluginId: 'api-docs', + info: { packageJson: () => import('../package.json') }, routes: { root: convertLegacyRouteRef(rootRoute), }, diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md index b068dec109..42beb0377f 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-app-backend +## 0.5.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/config-loader@1.10.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-app-node@0.1.34-next.1 + +## 0.5.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/config-loader@1.10.1 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-app-node@0.1.34-next.0 + ## 0.5.2 ### Patch Changes diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index c3d2ceaa42..6a2a2ea09b 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.2", + "version": "0.5.3-next.1", "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 71809c0fa2..4052664edc 100644 --- a/plugins/app-node/CHANGELOG.md +++ b/plugins/app-node/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-app-node +## 0.1.34-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config-loader@1.10.1 + +## 0.1.34-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/config-loader@1.10.1 + ## 0.1.33 ### Patch Changes diff --git a/plugins/app-node/package.json b/plugins/app-node/package.json index ab4c0fa1f6..83d0009c1b 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.33", + "version": "0.1.34-next.1", "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 ef306d3e3e..e5ca9aab39 100644 --- a/plugins/app-visualizer/CHANGELOG.md +++ b/plugins/app-visualizer/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-app-visualizer +## 0.1.20-next.0 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + ## 0.1.19 ### Patch Changes diff --git a/plugins/app-visualizer/package.json b/plugins/app-visualizer/package.json index c1f08eacee..7f3d005c0e 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.19", + "version": "0.1.20-next.0", "description": "Visualizes the Backstage app structure", "backstage": { "role": "frontend-plugin", diff --git a/plugins/app-visualizer/src/plugin.tsx b/plugins/app-visualizer/src/plugin.tsx index 92ccd00159..faaf2a5788 100644 --- a/plugins/app-visualizer/src/plugin.tsx +++ b/plugins/app-visualizer/src/plugin.tsx @@ -46,5 +46,6 @@ export const appVisualizerNavItem = NavItemBlueprint.make({ /** @public */ export const visualizerPlugin = createFrontendPlugin({ pluginId: 'app-visualizer', + info: { packageJson: () => import('../package.json') }, extensions: [appVisualizerPage, appVisualizerNavItem], }); diff --git a/plugins/app/CHANGELOG.md b/plugins/app/CHANGELOG.md index 4394a3aa19..7ef336e214 100644 --- a/plugins/app/CHANGELOG.md +++ b/plugins/app/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-app +## 0.1.10-next.0 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/integration-react@1.2.7 + ## 0.1.9 ### Patch Changes diff --git a/plugins/app/package.json b/plugins/app/package.json index f0fa7865fc..717a4cc2c4 100644 --- a/plugins/app/package.json +++ b/plugins/app/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app", - "version": "0.1.9", + "version": "0.1.10-next.0", "backstage": { "role": "frontend-plugin", "pluginId": "app", diff --git a/plugins/app/src/plugin.ts b/plugins/app/src/plugin.ts index b166423fa1..a1e6602410 100644 --- a/plugins/app/src/plugin.ts +++ b/plugins/app/src/plugin.ts @@ -42,6 +42,7 @@ import { apis } from './defaultApis'; /** @public */ export const appPlugin = createFrontendPlugin({ pluginId: 'app', + info: { packageJson: () => import('../package.json') }, extensions: [ ...apis, App, diff --git a/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md b/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md index b50266b2c1..3ff30201af 100644 --- a/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-auth-backend-module-atlassian-provider +## 0.4.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + ## 0.4.3 ### Patch Changes diff --git a/plugins/auth-backend-module-atlassian-provider/package.json b/plugins/auth-backend-module-atlassian-provider/package.json index e7d7b55357..c146a3a551 100644 --- a/plugins/auth-backend-module-atlassian-provider/package.json +++ b/plugins/auth-backend-module-atlassian-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-atlassian-provider", - "version": "0.4.3", + "version": "0.4.4-next.1", "description": "The atlassian-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-auth0-provider/CHANGELOG.md b/plugins/auth-backend-module-auth0-provider/CHANGELOG.md index ef51bc929a..22cec4e289 100644 --- a/plugins/auth-backend-module-auth0-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-auth0-provider/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-auth-backend-module-auth0-provider +## 0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + ## 0.2.3 ### Patch Changes diff --git a/plugins/auth-backend-module-auth0-provider/package.json b/plugins/auth-backend-module-auth0-provider/package.json index 72bfb9e690..3e18951293 100644 --- a/plugins/auth-backend-module-auth0-provider/package.json +++ b/plugins/auth-backend-module-auth0-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-auth0-provider", - "version": "0.2.3", + "version": "0.2.4-next.1", "description": "The auth0-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md index 296950a342..a681992dc8 100644 --- a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-auth-backend-module-aws-alb-provider +## 0.4.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/plugin-auth-backend@0.25.1-next.1 + +## 0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-backend@0.25.1-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + ## 0.4.3 ### Patch Changes diff --git a/plugins/auth-backend-module-aws-alb-provider/package.json b/plugins/auth-backend-module-aws-alb-provider/package.json index 03cbd6a0eb..4382931db3 100644 --- a/plugins/auth-backend-module-aws-alb-provider/package.json +++ b/plugins/auth-backend-module-aws-alb-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-aws-alb-provider", - "version": "0.4.3", + "version": "0.4.4-next.1", "description": "The aws-alb provider module for the Backstage auth backend.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md b/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md index 43d64a9cbc..f0359b1e55 100644 --- a/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-auth-backend-module-azure-easyauth-provider +## 0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/errors@1.2.7 + +## 0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + ## 0.2.8 ### Patch Changes diff --git a/plugins/auth-backend-module-azure-easyauth-provider/package.json b/plugins/auth-backend-module-azure-easyauth-provider/package.json index 9d718233d1..39ebf38dd6 100644 --- a/plugins/auth-backend-module-azure-easyauth-provider/package.json +++ b/plugins/auth-backend-module-azure-easyauth-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-azure-easyauth-provider", - "version": "0.2.8", + "version": "0.2.9-next.1", "description": "The azure-easyauth-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md b/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md index ccc8c58794..a9c90e8a05 100644 --- a/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-auth-backend-module-bitbucket-provider +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + ## 0.3.3 ### Patch Changes diff --git a/plugins/auth-backend-module-bitbucket-provider/package.json b/plugins/auth-backend-module-bitbucket-provider/package.json index 82f5d79345..edd1d811da 100644 --- a/plugins/auth-backend-module-bitbucket-provider/package.json +++ b/plugins/auth-backend-module-bitbucket-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-bitbucket-provider", - "version": "0.3.3", + "version": "0.3.4-next.1", "description": "The bitbucket-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-bitbucket-server-provider/CHANGELOG.md b/plugins/auth-backend-module-bitbucket-server-provider/CHANGELOG.md index 88084fefed..7f2c2eac5c 100644 --- a/plugins/auth-backend-module-bitbucket-server-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-bitbucket-server-provider/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-auth-backend-module-bitbucket-server-provider +## 0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + ## 0.2.3 ### Patch Changes diff --git a/plugins/auth-backend-module-bitbucket-server-provider/package.json b/plugins/auth-backend-module-bitbucket-server-provider/package.json index 88f10ba209..7c2060a6df 100644 --- a/plugins/auth-backend-module-bitbucket-server-provider/package.json +++ b/plugins/auth-backend-module-bitbucket-server-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-bitbucket-server-provider", - "version": "0.2.3", + "version": "0.2.4-next.1", "description": "The bitbucket-server-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md b/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md index 656d4acce3..1e206cdbaa 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-auth-backend-module-cloudflare-access-provider +## 0.4.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + ## 0.4.3 ### Patch Changes diff --git a/plugins/auth-backend-module-cloudflare-access-provider/package.json b/plugins/auth-backend-module-cloudflare-access-provider/package.json index b8bf612cef..fc9334f958 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/package.json +++ b/plugins/auth-backend-module-cloudflare-access-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-cloudflare-access-provider", - "version": "0.4.3", + "version": "0.4.4-next.1", "description": "The cloudflare-access-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md b/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md index a12e01602e..63b30dba1f 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-auth-backend-module-gcp-iap-provider +## 0.4.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + ## 0.4.3 ### Patch Changes diff --git a/plugins/auth-backend-module-gcp-iap-provider/package.json b/plugins/auth-backend-module-gcp-iap-provider/package.json index 28e0999616..bbdeca08a3 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/package.json +++ b/plugins/auth-backend-module-gcp-iap-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-gcp-iap-provider", - "version": "0.4.3", + "version": "0.4.4-next.1", "description": "A GCP IAP auth provider module for the Backstage auth backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-github-provider/CHANGELOG.md b/plugins/auth-backend-module-github-provider/CHANGELOG.md index abe3bdce14..ec3305ddca 100644 --- a/plugins/auth-backend-module-github-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-github-provider/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-auth-backend-module-github-provider +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + ## 0.3.3 ### Patch Changes diff --git a/plugins/auth-backend-module-github-provider/package.json b/plugins/auth-backend-module-github-provider/package.json index 132a5460d4..92352dd8aa 100644 --- a/plugins/auth-backend-module-github-provider/package.json +++ b/plugins/auth-backend-module-github-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-github-provider", - "version": "0.3.3", + "version": "0.3.4-next.1", "description": "The github-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md b/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md index a47629b62a..94c6a8a63c 100644 --- a/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-auth-backend-module-gitlab-provider +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + ## 0.3.3 ### Patch Changes diff --git a/plugins/auth-backend-module-gitlab-provider/package.json b/plugins/auth-backend-module-gitlab-provider/package.json index 2ea5f593bd..8db0c5f881 100644 --- a/plugins/auth-backend-module-gitlab-provider/package.json +++ b/plugins/auth-backend-module-gitlab-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-gitlab-provider", - "version": "0.3.3", + "version": "0.3.4-next.1", "description": "The gitlab-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-google-provider/CHANGELOG.md b/plugins/auth-backend-module-google-provider/CHANGELOG.md index 89e23af93c..451746eb0a 100644 --- a/plugins/auth-backend-module-google-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-google-provider/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-auth-backend-module-google-provider +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + ## 0.3.3 ### Patch Changes diff --git a/plugins/auth-backend-module-google-provider/package.json b/plugins/auth-backend-module-google-provider/package.json index 36774d18e8..d0c77c3bde 100644 --- a/plugins/auth-backend-module-google-provider/package.json +++ b/plugins/auth-backend-module-google-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-google-provider", - "version": "0.3.3", + "version": "0.3.4-next.1", "description": "A Google auth provider module for the Backstage auth backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-guest-provider/CHANGELOG.md b/plugins/auth-backend-module-guest-provider/CHANGELOG.md index 0265485261..c9117fb1e2 100644 --- a/plugins/auth-backend-module-guest-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-guest-provider/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-auth-backend-module-guest-provider +## 0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/errors@1.2.7 + +## 0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + ## 0.2.8 ### Patch Changes diff --git a/plugins/auth-backend-module-guest-provider/package.json b/plugins/auth-backend-module-guest-provider/package.json index bf5d97adc1..92368c3364 100644 --- a/plugins/auth-backend-module-guest-provider/package.json +++ b/plugins/auth-backend-module-guest-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-guest-provider", - "version": "0.2.8", + "version": "0.2.9-next.1", "description": "The guest-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md b/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md index e39bb2b21f..3ddacc4be9 100644 --- a/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-auth-backend-module-microsoft-provider +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + ## 0.3.3 ### Patch Changes diff --git a/plugins/auth-backend-module-microsoft-provider/package.json b/plugins/auth-backend-module-microsoft-provider/package.json index e22ac200b3..d3ecdcbc4d 100644 --- a/plugins/auth-backend-module-microsoft-provider/package.json +++ b/plugins/auth-backend-module-microsoft-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-microsoft-provider", - "version": "0.3.3", + "version": "0.3.4-next.1", "description": "The microsoft-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md b/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md index 92d6245a94..67fcf5686d 100644 --- a/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-auth-backend-module-oauth2-provider +## 0.4.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + ## 0.4.3 ### Patch Changes diff --git a/plugins/auth-backend-module-oauth2-provider/package.json b/plugins/auth-backend-module-oauth2-provider/package.json index 8b080fbc4c..33c221ce42 100644 --- a/plugins/auth-backend-module-oauth2-provider/package.json +++ b/plugins/auth-backend-module-oauth2-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-oauth2-provider", - "version": "0.4.3", + "version": "0.4.4-next.1", "description": "The oauth2-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md b/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md index d38a9762ab..6da4b2529d 100644 --- a/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-auth-backend-module-oauth2-proxy-provider +## 0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/errors@1.2.7 + +## 0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + ## 0.2.8 ### Patch Changes diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/package.json b/plugins/auth-backend-module-oauth2-proxy-provider/package.json index 9d497751b0..2df01732ac 100644 --- a/plugins/auth-backend-module-oauth2-proxy-provider/package.json +++ b/plugins/auth-backend-module-oauth2-proxy-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-oauth2-proxy-provider", - "version": "0.2.8", + "version": "0.2.9-next.1", "description": "The oauth2-proxy-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md index 27b60a500e..37f529cbbc 100644 --- a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-auth-backend-module-oidc-provider +## 0.4.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-auth-backend@0.25.1-next.1 + +## 0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-backend@0.25.1-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + ## 0.4.3 ### Patch Changes diff --git a/plugins/auth-backend-module-oidc-provider/package.json b/plugins/auth-backend-module-oidc-provider/package.json index e52744dfe5..621726e6c4 100644 --- a/plugins/auth-backend-module-oidc-provider/package.json +++ b/plugins/auth-backend-module-oidc-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-oidc-provider", - "version": "0.4.3", + "version": "0.4.4-next.1", "description": "The oidc-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-okta-provider/CHANGELOG.md b/plugins/auth-backend-module-okta-provider/CHANGELOG.md index 7d71f0090d..2c4022fc73 100644 --- a/plugins/auth-backend-module-okta-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-okta-provider/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-auth-backend-module-okta-provider +## 0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + ## 0.2.3 ### Patch Changes diff --git a/plugins/auth-backend-module-okta-provider/package.json b/plugins/auth-backend-module-okta-provider/package.json index b47aa64a6c..2780942fe8 100644 --- a/plugins/auth-backend-module-okta-provider/package.json +++ b/plugins/auth-backend-module-okta-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-okta-provider", - "version": "0.2.3", + "version": "0.2.4-next.1", "description": "The okta-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md b/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md index dd5fcd958d..dce3cd0c6c 100644 --- a/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-auth-backend-module-onelogin-provider +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + ## 0.3.3 ### Patch Changes diff --git a/plugins/auth-backend-module-onelogin-provider/package.json b/plugins/auth-backend-module-onelogin-provider/package.json index 8340158a57..58f5489bbc 100644 --- a/plugins/auth-backend-module-onelogin-provider/package.json +++ b/plugins/auth-backend-module-onelogin-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-onelogin-provider", - "version": "0.3.3", + "version": "0.3.4-next.1", "description": "The onelogin-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md b/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md index 6dfbd5f545..0c31e3d28e 100644 --- a/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-auth-backend-module-pinniped-provider +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + ## 0.3.3 ### Patch Changes diff --git a/plugins/auth-backend-module-pinniped-provider/package.json b/plugins/auth-backend-module-pinniped-provider/package.json index 2b9ca4cf51..4cde024617 100644 --- a/plugins/auth-backend-module-pinniped-provider/package.json +++ b/plugins/auth-backend-module-pinniped-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-pinniped-provider", - "version": "0.3.3", + "version": "0.3.4-next.1", "description": "The pinniped-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md b/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md index 10d257c583..e8e5ac6a05 100644 --- a/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-auth-backend-module-vmware-cloud-provider +## 0.5.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + +## 0.5.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + ## 0.5.3 ### Patch Changes diff --git a/plugins/auth-backend-module-vmware-cloud-provider/package.json b/plugins/auth-backend-module-vmware-cloud-provider/package.json index b014ae7697..2aa4cc56bc 100644 --- a/plugins/auth-backend-module-vmware-cloud-provider/package.json +++ b/plugins/auth-backend-module-vmware-cloud-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-vmware-cloud-provider", - "version": "0.5.3", + "version": "0.5.4-next.1", "description": "The vmware-cloud-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index c9a4f2f532..0c97d879ae 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-auth-backend +## 0.25.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.25.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + ## 0.25.0 ### Minor Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index b1f1e4360b..9ba97ee8ac 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.25.0", + "version": "0.25.1-next.1", "description": "A Backstage backend plugin that handles authentication", "backstage": { "role": "backend-plugin", diff --git a/plugins/auth-node/CHANGELOG.md b/plugins/auth-node/CHANGELOG.md index ab8e1c8626..5745219625 100644 --- a/plugins/auth-node/CHANGELOG.md +++ b/plugins/auth-node/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-auth-node +## 0.6.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.6.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + ## 0.6.3 ### Patch Changes diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index 0157935cb6..fe24d798d6 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-node", - "version": "0.6.3", + "version": "0.6.4-next.1", "backstage": { "role": "node-library", "pluginId": "auth", diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md index 0e438f5f7f..95417d8073 100644 --- a/plugins/catalog-backend-module-aws/CHANGELOG.md +++ b/plugins/catalog-backend-module-aws/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-catalog-backend-module-aws +## 0.4.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-defaults@0.10.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/integration-aws-node@0.1.16 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-kubernetes-common@0.9.5 + +## 0.4.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/backend-defaults@0.10.1-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + ## 0.4.11 ### Patch Changes diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index d96aa2dfc8..e1259daa7c 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.11", + "version": "0.4.12-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 203a6eea03..c59dfea5b3 100644 --- a/plugins/catalog-backend-module-azure/CHANGELOG.md +++ b/plugins/catalog-backend-module-azure/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-catalog-backend-module-azure +## 0.3.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + ## 0.3.5 ### Patch Changes diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index 2f0d5084ea..cfc5f2a30d 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.5", + "version": "0.3.6-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 0bb42e2274..f2282c7796 100644 --- a/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-catalog-backend-module-backstage-openapi +## 0.5.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-openapi-utils@0.5.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.5.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/backend-openapi-utils@0.5.4-next.0 + ## 0.5.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-backstage-openapi/package.json b/plugins/catalog-backend-module-backstage-openapi/package.json index 8c4f7c9d2c..b3669aa611 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.2", + "version": "0.5.3-next.1", "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 880e4907df..bbab017765 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-catalog-backend-module-bitbucket-cloud +## 0.4.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/integration@1.17.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.0 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 0.4.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + ## 0.4.8 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json index 6c3cd8bf50..b0f4c08403 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.4.8", + "version": "0.4.9-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/report.api.md b/plugins/catalog-backend-module-bitbucket-cloud/report.api.md index 77b20e44f1..74fc90680e 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/report.api.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/report.api.md @@ -5,7 +5,7 @@ ```ts import { AuthService } from '@backstage/backend-plugin-api'; import { BackendFeature } from '@backstage/backend-plugin-api'; -import { CatalogApi } from '@backstage/catalog-client'; +import { CatalogService } from '@backstage/plugin-catalog-node'; import { Config } from '@backstage/config'; import { EntityProvider } from '@backstage/plugin-catalog-node'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; @@ -23,7 +23,7 @@ export class BitbucketCloudEntityProvider implements EntityProvider { config: Config, options: { auth: AuthService; - catalogApi: CatalogApi; + catalog: CatalogService; events: EventsService; logger: LoggerService; schedule?: SchedulerServiceTaskRunner; diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.ts index 91f20c157c..3ff6556d40 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.ts @@ -18,10 +18,8 @@ import { coreServices, createBackendModule, } from '@backstage/backend-plugin-api'; -import { - catalogProcessingExtensionPoint, - catalogServiceRef, -} from '@backstage/plugin-catalog-node/alpha'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; +import { catalogServiceRef } from '@backstage/plugin-catalog-node'; import { eventsServiceRef } from '@backstage/plugin-events-node'; import { BitbucketCloudEntityProvider } from '../providers/BitbucketCloudEntityProvider'; @@ -35,8 +33,8 @@ export const catalogModuleBitbucketCloudEntityProvider = createBackendModule({ env.registerInit({ deps: { auth: coreServices.auth, - catalog: catalogProcessingExtensionPoint, - catalogApi: catalogServiceRef, + catalogProcessing: catalogProcessingExtensionPoint, + catalog: catalogServiceRef, config: coreServices.rootConfig, events: eventsServiceRef, logger: coreServices.logger, @@ -44,8 +42,8 @@ export const catalogModuleBitbucketCloudEntityProvider = createBackendModule({ }, async init({ auth, + catalogProcessing, catalog, - catalogApi, config, events, logger, @@ -53,13 +51,13 @@ export const catalogModuleBitbucketCloudEntityProvider = createBackendModule({ }) { const providers = BitbucketCloudEntityProvider.fromConfig(config, { auth, - catalogApi, + catalog, events, logger, scheduler, }); - catalog.addEntityProvider(providers); + catalogProcessing.addEntityProvider(providers); }, }); }, diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.test.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.test.ts index 6ea92dac96..1b8d4b4e03 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.test.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.test.ts @@ -15,6 +15,7 @@ */ import { + BackstageCredentials, SchedulerServiceTaskInvocationDefinition, SchedulerServiceTaskRunner, } from '@backstage/backend-plugin-api'; @@ -201,13 +202,13 @@ describe('BitbucketCloudEntityProvider', () => { }); it('no provider config', () => { - const auth = mockServices.auth.mock(); - const catalogApi = catalogServiceMock(); + const auth = mockServices.auth(); + const catalog = catalogServiceMock(); const config = new ConfigReader({}); const events = DefaultEventsService.create({ logger }); const providers = BitbucketCloudEntityProvider.fromConfig(config, { auth, - catalogApi, + catalog, events, logger, schedule, @@ -217,12 +218,12 @@ describe('BitbucketCloudEntityProvider', () => { }); it('single simple provider config', () => { - const auth = mockServices.auth.mock(); - const catalogApi = catalogServiceMock(); + const auth = mockServices.auth(); + const catalog = catalogServiceMock(); const events = DefaultEventsService.create({ logger }); const providers = BitbucketCloudEntityProvider.fromConfig(simpleConfig, { auth, - catalogApi, + catalog, events, logger, schedule, @@ -235,14 +236,14 @@ describe('BitbucketCloudEntityProvider', () => { }); it('fail without schedule and scheduler', () => { - const auth = mockServices.auth.mock(); - const catalogApi = catalogServiceMock(); + const auth = mockServices.auth(); + const catalog = catalogServiceMock(); const events = DefaultEventsService.create({ logger }); expect(() => BitbucketCloudEntityProvider.fromConfig(simpleConfig, { auth, - catalogApi, + catalog, events, logger, }), @@ -250,8 +251,8 @@ describe('BitbucketCloudEntityProvider', () => { }); it('fail with scheduler but no schedule config', () => { - const auth = mockServices.auth.mock(); - const catalogApi = catalogServiceMock(); + const auth = mockServices.auth(); + const catalog = catalogServiceMock(); const events = DefaultEventsService.create({ logger }); const scheduler = mockServices.scheduler.mock(); const config = new ConfigReader({ @@ -267,7 +268,7 @@ describe('BitbucketCloudEntityProvider', () => { expect(() => BitbucketCloudEntityProvider.fromConfig(config, { auth, - catalogApi, + catalog, events, logger, scheduler, @@ -278,8 +279,8 @@ describe('BitbucketCloudEntityProvider', () => { }); it('single simple provider config with schedule in config', () => { - const auth = mockServices.auth.mock(); - const catalogApi = catalogServiceMock(); + const auth = mockServices.auth(); + const catalog = catalogServiceMock(); const events = DefaultEventsService.create({ logger }); const scheduler = mockServices.scheduler.mock(); const config = new ConfigReader({ @@ -298,7 +299,7 @@ describe('BitbucketCloudEntityProvider', () => { const providers = BitbucketCloudEntityProvider.fromConfig(config, { auth, - catalogApi, + catalog, events, logger, scheduler, @@ -311,8 +312,8 @@ describe('BitbucketCloudEntityProvider', () => { }); it('multiple provider configs', () => { - const auth = mockServices.auth.mock(); - const catalogApi = catalogServiceMock(); + const auth = mockServices.auth(); + const catalog = catalogServiceMock(); const config = new ConfigReader({ catalog: { providers: { @@ -330,7 +331,7 @@ describe('BitbucketCloudEntityProvider', () => { const events = DefaultEventsService.create({ logger }); const providers = BitbucketCloudEntityProvider.fromConfig(config, { auth, - catalogApi, + catalog, events, logger, schedule, @@ -346,12 +347,12 @@ describe('BitbucketCloudEntityProvider', () => { }); it('apply full update on scheduled execution', async () => { - const auth = mockServices.auth.mock(); - const catalogApi = catalogServiceMock(); + const auth = mockServices.auth(); + const catalog = catalogServiceMock(); const events = DefaultEventsService.create({ logger }); const provider = BitbucketCloudEntityProvider.fromConfig(defaultConfig, { auth, - catalogApi, + catalog, events, logger, schedule, @@ -559,17 +560,14 @@ describe('BitbucketCloudEntityProvider', () => { 'added-module/catalog-custom.yaml', ); - const auth = mockServices.auth.mock({ - getPluginRequestToken: async () => ({ token: 'fake-token' }), - }); + const auth = mockServices.auth(); const events = DefaultEventsService.create({ logger }); - const catalogApi = catalogServiceMock.mock({ + const catalog = catalogServiceMock.mock({ getEntities: async ( request: { filter: Record }, - options: { token: string }, + _options: { credentials: BackstageCredentials }, ): Promise<{ items: Entity[] }> => { if ( - options.token !== 'fake-token' || request.filter.kind !== 'Location' || request.filter['metadata.annotations.bitbucket.org/repo-url'] !== 'https://bitbucket.org/test-ws/test-repo' @@ -584,7 +582,7 @@ describe('BitbucketCloudEntityProvider', () => { }); const provider = BitbucketCloudEntityProvider.fromConfig(defaultConfig, { auth, - catalogApi, + catalog, events, logger, schedule, @@ -695,13 +693,13 @@ describe('BitbucketCloudEntityProvider', () => { }); it('no onRepoPush update on non-matching workspace slug', async () => { - const auth = mockServices.auth.mock(); - const catalogApi = catalogServiceMock(); - jest.spyOn(catalogApi, 'refreshEntity'); + const auth = mockServices.auth(); + const catalog = catalogServiceMock(); + jest.spyOn(catalog, 'refreshEntity'); const events = DefaultEventsService.create({ logger }); const provider = BitbucketCloudEntityProvider.fromConfig(defaultConfig, { auth, - catalogApi, + catalog, events, logger, schedule, @@ -722,18 +720,18 @@ describe('BitbucketCloudEntityProvider', () => { }, }); - expect(catalogApi.refreshEntity).toHaveBeenCalledTimes(0); + expect(catalog.refreshEntity).toHaveBeenCalledTimes(0); expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0); }); it('no onRepoPush update on non-matching repo slug', async () => { - const auth = mockServices.auth.mock(); - const catalogApi = catalogServiceMock(); - jest.spyOn(catalogApi, 'refreshEntity'); + const auth = mockServices.auth(); + const catalog = catalogServiceMock(); + jest.spyOn(catalog, 'refreshEntity'); const events = DefaultEventsService.create({ logger }); const provider = BitbucketCloudEntityProvider.fromConfig(defaultConfig, { auth, - catalogApi, + catalog, events, logger, schedule, @@ -751,7 +749,7 @@ describe('BitbucketCloudEntityProvider', () => { }, }); - expect(catalogApi.refreshEntity).toHaveBeenCalledTimes(0); + expect(catalog.refreshEntity).toHaveBeenCalledTimes(0); expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0); }); @@ -767,17 +765,14 @@ describe('BitbucketCloudEntityProvider', () => { 'module/catalog-custom.yaml', ); - const auth = mockServices.auth.mock({ - getPluginRequestToken: async () => ({ token: 'fake-token' }), - }); + const auth = mockServices.auth(); const events = DefaultEventsService.create({ logger }); - const catalogApi = catalogServiceMock.mock({ + const catalog = catalogServiceMock.mock({ getEntities: async ( request: { filter: Record }, - options: { token: string }, + _options: { credentials: BackstageCredentials }, ): Promise<{ items: Entity[] }> => { if ( - options.token !== 'fake-token' || request.filter.kind !== 'Location' || request.filter['metadata.annotations.bitbucket.org/repo-url'] !== 'https://bitbucket.org/test-ws/test-repo-old' @@ -792,7 +787,7 @@ describe('BitbucketCloudEntityProvider', () => { }); const provider = BitbucketCloudEntityProvider.fromConfig(defaultConfig, { auth, - catalogApi, + catalog, events, logger, schedule, @@ -870,13 +865,13 @@ describe('BitbucketCloudEntityProvider', () => { }); it('no onRepoUpdated update on non-matching workspace slug', async () => { - const auth = mockServices.auth.mock(); - const catalogApi = catalogServiceMock(); - jest.spyOn(catalogApi, 'refreshEntity'); + const auth = mockServices.auth(); + const catalog = catalogServiceMock(); + jest.spyOn(catalog, 'refreshEntity'); const events = DefaultEventsService.create({ logger }); const provider = BitbucketCloudEntityProvider.fromConfig(defaultConfig, { auth, - catalogApi, + catalog, events, logger, schedule, @@ -897,18 +892,18 @@ describe('BitbucketCloudEntityProvider', () => { }, }); - expect(catalogApi.refreshEntity).toHaveBeenCalledTimes(0); + expect(catalog.refreshEntity).toHaveBeenCalledTimes(0); expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0); }); it('no onRepoUpdated update on non-matching repo slug', async () => { - const auth = mockServices.auth.mock(); - const catalogApi = catalogServiceMock(); - jest.spyOn(catalogApi, 'refreshEntity'); + const auth = mockServices.auth(); + const catalog = catalogServiceMock(); + jest.spyOn(catalog, 'refreshEntity'); const events = DefaultEventsService.create({ logger }); const provider = BitbucketCloudEntityProvider.fromConfig(defaultConfig, { auth, - catalogApi, + catalog, events, logger, schedule, @@ -926,18 +921,18 @@ describe('BitbucketCloudEntityProvider', () => { }, }); - expect(catalogApi.refreshEntity).toHaveBeenCalledTimes(0); + expect(catalog.refreshEntity).toHaveBeenCalledTimes(0); expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0); }); it('no onRepoUpdated update on non-relevant repo update', async () => { - const auth = mockServices.auth.mock(); - const catalogApi = catalogServiceMock(); - jest.spyOn(catalogApi, 'refreshEntity'); + const auth = mockServices.auth(); + const catalog = catalogServiceMock(); + jest.spyOn(catalog, 'refreshEntity'); const events = DefaultEventsService.create({ logger }); const provider = BitbucketCloudEntityProvider.fromConfig(defaultConfig, { auth, - catalogApi, + catalog, events, logger, schedule, @@ -957,7 +952,7 @@ describe('BitbucketCloudEntityProvider', () => { }, }); - expect(catalogApi.refreshEntity).toHaveBeenCalledTimes(0); + expect(catalog.refreshEntity).toHaveBeenCalledTimes(0); expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0); }); }); 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 88d0cb80ef..64a58c2924 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.ts @@ -20,7 +20,6 @@ import { SchedulerService, SchedulerServiceTaskRunner, } from '@backstage/backend-plugin-api'; -import { CatalogApi } from '@backstage/catalog-client'; import { LocationEntity } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { @@ -33,6 +32,7 @@ import { Models, } from '@backstage/plugin-bitbucket-cloud-common'; import { + CatalogService, DeferredEntity, EntityProvider, EntityProviderConnection, @@ -68,7 +68,7 @@ interface IngestionTarget { */ export class BitbucketCloudEntityProvider implements EntityProvider { private readonly auth: AuthService; - private readonly catalogApi: CatalogApi; + private readonly catalog: CatalogService; private readonly client: BitbucketCloudClient; private readonly config: BitbucketCloudEntityProviderConfig; private readonly events: EventsService; @@ -81,7 +81,7 @@ export class BitbucketCloudEntityProvider implements EntityProvider { config: Config, options: { auth: AuthService; - catalogApi: CatalogApi; + catalog: CatalogService; events: EventsService; logger: LoggerService; schedule?: SchedulerServiceTaskRunner; @@ -113,7 +113,7 @@ export class BitbucketCloudEntityProvider implements EntityProvider { return new BitbucketCloudEntityProvider( options.auth, - options.catalogApi, + options.catalog, providerConfig, options.events, integration, @@ -125,7 +125,7 @@ export class BitbucketCloudEntityProvider implements EntityProvider { private constructor( auth: AuthService, - catalogApi: CatalogApi, + catalog: CatalogService, config: BitbucketCloudEntityProviderConfig, events: EventsService, integration: BitbucketCloudIntegration, @@ -133,7 +133,7 @@ export class BitbucketCloudEntityProvider implements EntityProvider { taskRunner: SchedulerServiceTaskRunner, ) { this.auth = auth; - this.catalogApi = catalogApi; + this.catalog = catalog; this.client = BitbucketCloudClient.fromConfig(integration.config); this.config = config; this.events = events; @@ -350,13 +350,11 @@ export class BitbucketCloudEntityProvider implements EntityProvider { filter[`metadata.annotations.${ANNOTATION_BITBUCKET_CLOUD_REPO_URL}`] = repoUrl; - const { token } = await this.auth.getPluginRequestToken({ - onBehalfOf: await this.auth.getOwnServiceCredentials(), - targetPluginId: 'catalog', - }); - - return this.catalogApi - .getEntities({ filter }, { token }) + return this.catalog + .getEntities( + { filter }, + { credentials: await this.auth.getOwnServiceCredentials() }, + ) .then(result => result.items) as Promise; } diff --git a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md index 69fa32bc03..7506f728cf 100644 --- a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-catalog-backend-module-bitbucket-server +## 0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 0.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + ## 0.4.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-server/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json index 4d7b453b87..de7fb993e9 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.4.1", + "version": "0.4.2-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 61399df5f6..99df738894 100644 --- a/plugins/catalog-backend-module-gcp/CHANGELOG.md +++ b/plugins/catalog-backend-module-gcp/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-catalog-backend-module-gcp +## 0.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/plugin-kubernetes-common@0.9.5 + +## 0.3.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + ## 0.3.8 ### Patch Changes diff --git a/plugins/catalog-backend-module-gcp/package.json b/plugins/catalog-backend-module-gcp/package.json index 2b52711f61..dbcd8c8249 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.8", + "version": "0.3.9-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 76e546da9f..104a13390d 100644 --- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md +++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-catalog-backend-module-gerrit +## 0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + ## 0.3.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index 816ad8f311..5568702238 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.2", + "version": "0.3.3-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 719caa1163..849f8205b4 100644 --- a/plugins/catalog-backend-module-gitea/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitea/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-catalog-backend-module-gitea +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + ## 0.1.0 ### Minor Changes diff --git a/plugins/catalog-backend-module-gitea/package.json b/plugins/catalog-backend-module-gitea/package.json index 732cd7066e..714252d43a 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.0", + "version": "0.1.1-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 4b7c8dbf2f..8e0cb68570 100644 --- a/plugins/catalog-backend-module-github-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-github-org/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-catalog-backend-module-github-org +## 0.3.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-github@0.9.1-next.1 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-backend-module-github@0.9.1-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + ## 0.3.10 ### Patch Changes diff --git a/plugins/catalog-backend-module-github-org/package.json b/plugins/catalog-backend-module-github-org/package.json index 00dcb3d18c..438ec45dcf 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.10", + "version": "0.3.11-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 283daad98f..11ff006b82 100644 --- a/plugins/catalog-backend-module-github/CHANGELOG.md +++ b/plugins/catalog-backend-module-github/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-catalog-backend-module-github +## 0.9.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-catalog-backend@2.0.1-next.1 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 0.9.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@2.0.1-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + ## 0.9.0 ### Minor Changes diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index 5db2939352..95fae59471 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.9.0", + "version": "0.9.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 1144bb804f..87609736c4 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.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-gitlab@0.6.7-next.1 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-backend-module-gitlab@0.6.7-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + ## 0.2.9 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab-org/package.json b/plugins/catalog-backend-module-gitlab-org/package.json index d92ddd5de7..6240674608 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.9", + "version": "0.2.10-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 60e8ca66cf..790d906cd9 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-catalog-backend-module-gitlab +## 0.6.7-next.1 + +### Patch Changes + +- 57a0bad: Implement retry for GitLab API calls to handle rate limiting +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-defaults@0.10.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 0.6.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/backend-defaults@0.10.1-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + ## 0.6.6 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index 0b8738c80e..6dea628d79 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.6.6", + "version": "0.6.7-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/__testUtils__/handlers.ts b/plugins/catalog-backend-module-gitlab/src/__testUtils__/handlers.ts index 9ffb43a53d..0732bbc4cd 100644 --- a/plugins/catalog-backend-module-gitlab/src/__testUtils__/handlers.ts +++ b/plugins/catalog-backend-module-gitlab/src/__testUtils__/handlers.ts @@ -30,6 +30,9 @@ import { some_endpoint, unhealthy_endpoint, userID, + all_saas_subgroup_1_members, + all_saas_subgroup_2_members, + group_with_subgroups_response, } from './mocks'; const httpHandlers = [ @@ -71,6 +74,13 @@ const httpHandlers = [ return res(ctx.set('x-next-page', ''), ctx.json(all_groups_response)); }), + rest.get(`${apiBaseUrl}/groups/group-with-subgroup`, (_, res, ctx) => { + return res( + ctx.set('x-next-page', ''), + ctx.json(group_with_subgroups_response), + ); + }), + rest.get(`${apiBaseUrl}/groups/42`, (_, res, ctx) => { return res(ctx.status(500), ctx.json({ error: 'Internal Server Error' })); }), @@ -89,6 +99,24 @@ const httpHandlers = [ }, ), + rest.get(`${apiBaseUrlSaas}/groups/456/members/all`, (_req, res, ctx) => { + return res(ctx.json(all_saas_users_response)); + }), + + rest.get(`${apiBaseUrlSaas}/groups/1/members/all`, (_req, res, ctx) => { + return res(ctx.json(all_saas_users_response)); + }), + + // Subgroup 1 members id=6 + rest.get(`${apiBaseUrlSaas}/groups/6/members/all`, (_req, res, ctx) => { + return res(ctx.json(all_saas_subgroup_1_members)); + }), + + // Subgroup 2 members id=7 + rest.get(`${apiBaseUrlSaas}/groups/7/members/all`, (_req, res, ctx) => { + return res(ctx.json(all_saas_subgroup_2_members)); + }), + /** * Users REST endpoint mocks */ @@ -608,7 +636,47 @@ const graphqlHandlers = [ graphql .link(saasGraphQlBaseUrl) - .query('listDescendantGroups', async (_, res, ctx) => { + .query('listDescendantGroups', async (req, res, ctx) => { + const { group } = req.variables; + + if (group === 'group1') { + return res( + ctx.data({ + group: { + descendantGroups: { + nodes: req.variables.endCursor + ? [ + { + id: 'gid://gitlab/Group/6', + name: 'subgroup1', + description: 'description1', + fullPath: 'path/subgroup1', + parent: { + id: '1', + }, + }, + ] + : [ + { + id: 'gid://gitlab/Group/7', + name: 'subgroup2', + description: 'description2', + fullPath: 'path/subgroup2', + parent: { + id: '1', + }, + }, + ], + pageInfo: { + endCursor: req.variables.endCursor ? 'end' : 'next', + hasNextPage: !req.variables.endCursor, + }, + }, + }, + }), + ); + } + return res( ctx.data({ group: { diff --git a/plugins/catalog-backend-module-gitlab/src/__testUtils__/mocks.ts b/plugins/catalog-backend-module-gitlab/src/__testUtils__/mocks.ts index d9914c0d0c..2e74be66cc 100644 --- a/plugins/catalog-backend-module-gitlab/src/__testUtils__/mocks.ts +++ b/plugins/catalog-backend-module-gitlab/src/__testUtils__/mocks.ts @@ -796,6 +796,30 @@ export const config_org_group_restrictUsers_true_selfHosted = { }, }, }; + +export const config_org_group_with_subgroups_sass = { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + apiBaseUrl: 'https://gitlab.com/api/v4', + token: '1234', + }, + ], + }, + catalog: { + providers: { + gitlab: { + 'test-id': { + host: 'gitlab.com', + group: 'group1', + orgEnabled: true, + skipForkedRepos: true, + }, + }, + }, + }, +}; /** * GitLab API responses */ @@ -1020,7 +1044,72 @@ export const all_saas_users_response: MockObject[] = [ access_level: 30, created_at: '2023-07-19T08:58:34.984Z', expires_at: null, - id: 34, + id: 36, + username: 'testusernoseat1', + name: 'Test User No Seat 1', + state: 'active', + avatar_url: 'https://secure.gravatar.com/', + web_url: 'https://gitlab.com/testusernoseat1', + email: 'testusernoseat1@example.com', + group_saml_identity: { + provider: 'group_saml', + extern_uid: '60', + saml_provider_id: 1, + }, + is_using_seat: false, + membership_state: 'active', + }, +]; + +// Only test user 1 +export const all_saas_group_with_subgroups_members: MockObject[] = [ + { + access_level: 30, + created_at: '2023-07-17T08:58:34.984Z', + expires_at: null, + id: 12, + username: 'testuser1', + name: 'Test User 1', + state: 'active', + avatar_url: 'https://secure.gravatar.com/', + web_url: 'https://gitlab.com/testuser1', + email: 'testuser1@example.com', + group_saml_identity: { + provider: 'group_saml', + extern_uid: '50', + saml_provider_id: 1, + }, + is_using_seat: true, + membership_state: 'active', + }, +]; + +// Only test user 1 +export const all_saas_subgroup_1_members: MockObject[] = [ + { + access_level: 30, + created_at: '2023-07-17T08:58:34.984Z', + expires_at: null, + id: 12, + username: 'testuser1', + name: 'Test User 1', + state: 'active', + avatar_url: 'https://secure.gravatar.com/', + web_url: 'https://gitlab.com/testuser1', + email: 'testuser1@example.com', + group_saml_identity: { + provider: 'group_saml', + extern_uid: '51', + saml_provider_id: 1, + }, + is_using_seat: true, + membership_state: 'active', + }, + { + access_level: 30, + created_at: '2023-07-17T08:58:34.984Z', + expires_at: null, + id: 33, username: 'testuser3', name: 'Test User 3', state: 'active', @@ -1032,7 +1121,49 @@ export const all_saas_users_response: MockObject[] = [ extern_uid: '53', saml_provider_id: 1, }, - is_using_seat: false, + is_using_seat: true, + membership_state: 'active', + }, +]; + +// Only test user 2 +export const all_saas_subgroup_2_members: MockObject[] = [ + { + access_level: 30, + created_at: '2023-07-19T08:58:34.984Z', + expires_at: null, + id: 34, + username: 'testuser2', + name: 'Test User 2', + state: 'active', + avatar_url: 'https://secure.gravatar.com/', + web_url: 'https://gitlab.com/testuser2', + email: 'testuser2@example.com', + group_saml_identity: { + provider: 'group_saml', + extern_uid: '52', + saml_provider_id: 1, + }, + is_using_seat: true, + membership_state: 'active', + }, + { + access_level: 30, + created_at: '2023-07-17T08:58:34.984Z', + expires_at: null, + id: 44, + username: 'testuser4', + name: 'Test User 4', + state: 'active', + avatar_url: 'https://secure.gravatar.com/', + web_url: 'https://gitlab.com/testuser4', + email: 'testuser4@example.com', + group_saml_identity: { + provider: 'group_saml', + extern_uid: '54', + saml_provider_id: 1, + }, + is_using_seat: true, membership_state: 'active', }, ]; @@ -1074,6 +1205,21 @@ export const all_groups_response: GitLabGroup[] = [ description: '', full_path: 'group1/subgroup1', }, + { + id: 7, + name: 'subgroup2', + description: '', + full_path: 'group1/subgroup2', + }, +]; + +export const group_with_subgroups_response: GitLabGroup[] = [ + { + id: 1, + name: 'group1', + description: 'description1', + full_path: 'group1', + }, ]; export const group_with_parent: MockObject[] = [ @@ -2183,6 +2329,58 @@ export const expected_full_org_scan_entities_saas: MockObject[] = [ }, locationKey: 'GitlabOrgDiscoveryEntityProvider:test-id', }, + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + annotations: { + 'backstage.io/managed-by-location': + 'url:https://gitlab.com/testuser4', + 'backstage.io/managed-by-origin-location': + 'url:https://gitlab.com/testuser4', + 'gitlab.com/user-login': 'https://gitlab.com/testuser4', + 'gitlab.com/saml-external-uid': '54', + }, + name: 'testuser4', + }, + spec: { + memberOf: [], + profile: { + displayName: 'Test User 4', + email: 'testuser4@example.com', + picture: 'https://secure.gravatar.com/', + }, + }, + }, + locationKey: 'GitlabOrgDiscoveryEntityProvider:test-id', + }, + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + annotations: { + 'backstage.io/managed-by-location': + 'url:https://gitlab.com/testuser3', + 'backstage.io/managed-by-origin-location': + 'url:https://gitlab.com/testuser3', + 'gitlab.com/user-login': 'https://gitlab.com/testuser3', + 'gitlab.com/saml-external-uid': '53', + }, + name: 'testuser3', + }, + spec: { + memberOf: [], + profile: { + displayName: 'Test User 3', + email: 'testuser3@example.com', + picture: 'https://secure.gravatar.com/', + }, + }, + }, + locationKey: 'GitlabOrgDiscoveryEntityProvider:test-id', + }, ]; export const expected_full_org_scan_entities_includeUsersWithoutSeat_saas: MockObject[] = @@ -2239,6 +2437,58 @@ export const expected_full_org_scan_entities_includeUsersWithoutSeat_saas: MockO }, locationKey: 'GitlabOrgDiscoveryEntityProvider:test-id', }, + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + annotations: { + 'backstage.io/managed-by-location': + 'url:https://gitlab.com/testusernoseat1', + 'backstage.io/managed-by-origin-location': + 'url:https://gitlab.com/testusernoseat1', + 'gitlab.com/user-login': 'https://gitlab.com/testusernoseat1', + 'gitlab.com/saml-external-uid': '60', + }, + name: 'testusernoseat1', + }, + spec: { + memberOf: [], + profile: { + displayName: 'Test User No Seat 1', + email: 'testusernoseat1@example.com', + picture: 'https://secure.gravatar.com/', + }, + }, + }, + locationKey: 'GitlabOrgDiscoveryEntityProvider:test-id', + }, + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + annotations: { + 'backstage.io/managed-by-location': + 'url:https://gitlab.com/testuser4', + 'backstage.io/managed-by-origin-location': + 'url:https://gitlab.com/testuser4', + 'gitlab.com/user-login': 'https://gitlab.com/testuser4', + 'gitlab.com/saml-external-uid': '54', + }, + name: 'testuser4', + }, + spec: { + memberOf: [], + profile: { + displayName: 'Test User 4', + email: 'testuser4@example.com', + picture: 'https://secure.gravatar.com/', + }, + }, + }, + locationKey: 'GitlabOrgDiscoveryEntityProvider:test-id', + }, { entity: { apiVersion: 'backstage.io/v1alpha1', diff --git a/plugins/catalog-backend-module-gitlab/src/lib/client.ts b/plugins/catalog-backend-module-gitlab/src/lib/client.ts index 9053d68554..e58227839e 100644 --- a/plugins/catalog-backend-module-gitlab/src/lib/client.ts +++ b/plugins/catalog-backend-module-gitlab/src/lib/client.ts @@ -190,9 +190,8 @@ export class GitLabClient { let endCursor: string | null = null; do { - const response: GitLabDescendantGroupsResponse = await fetch( - `${this.config.baseUrl}/api/graphql`, - { + const response: GitLabDescendantGroupsResponse = + await this.fetchWithRetry(`${this.config.baseUrl}/api/graphql`, { method: 'POST', headers: { ...getGitLabRequestOptions(this.config).headers, @@ -223,8 +222,7 @@ export class GitLabClient { } `, }), - }, - ).then(r => r.json()); + }).then(r => r.json()); if (response.errors) { throw new Error(`GraphQL errors: ${JSON.stringify(response.errors)}`); } @@ -266,7 +264,7 @@ export class GitLabClient { let hasNextPage: boolean = false; let endCursor: string | null = null; do { - const response: GitLabGroupMembersResponse = await fetch( + const response: GitLabGroupMembersResponse = await this.fetchWithRetry( `${this.config.baseUrl}/api/graphql`, { method: 'POST', @@ -359,7 +357,7 @@ export class GitLabClient { const request = new URL(`${this.config.apiBaseUrl}${endpoint}`); request.searchParams.append('ref', branch); - const response = await fetch(request.toString(), { + const response = await this.fetchWithRetry(request.toString(), { headers: getGitLabRequestOptions(this.config).headers, method: 'HEAD', }); @@ -406,7 +404,7 @@ export class GitLabClient { } this.logger.debug(`Fetching: ${request.toString()}`); - const response = await fetch( + const response = await this.fetchWithRetry( request.toString(), getGitLabRequestOptions(this.config), ); @@ -444,7 +442,7 @@ export class GitLabClient { } } - const response = await fetch( + const response = await this.fetchWithRetry( request.toString(), getGitLabRequestOptions(this.config), ); @@ -459,6 +457,48 @@ export class GitLabClient { return response.json(); } + + /** + * Performs a fetch request with retry logic for rate limiting (429 errors) + * @param url - The URL to fetch + * @param options - Fetch options + * @param retries - Maximum number of retries + * @param initialBackoff - Initial backoff time in ms + */ + async fetchWithRetry( + url: string, + options: fetch.RequestInit, + retries = 5, + initialBackoff = 100, + ): Promise { + let currentRetry = 0; + let backoff = initialBackoff; + + for (;;) { + const response = await fetch(url, options); + + if (response.status !== 429 || currentRetry >= retries) { + return response; + } + + // Get retry-after header if available, or use exponential backoff + const retryAfter = response.headers.get('Retry-After'); + const waitTime = retryAfter ? parseInt(retryAfter, 10) * 1000 : backoff; + + this.logger.warn( + `GitLab API rate limit exceeded, retrying in ${waitTime}ms (retry ${ + currentRetry + 1 + }/${retries})`, + ); + + // Wait before retrying + await new Promise(resolve => setTimeout(resolve, waitTime)); + + // Exponential backoff with jitter + backoff = backoff * 2 * (0.8 + Math.random() * 0.4); + currentRetry++; + } + } } /** diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts index 5f62cedb71..047488166a 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts @@ -303,7 +303,7 @@ describe('GitlabOrgDiscoveryEntityProvider - refresh', () => { }); }); - // This should return all members of the SaaS Root group (group1) -> expected_full_org_scan_entities_saas + // This should return all members of the SaaS Root group (group1) and any subgroups -> expected_full_org_scan_entities_saas it('SaaS: should get all saas root group users when restrictUsersToGroup is not set', async () => { const config = new ConfigReader(mock.config_org_group_saas); const schedule = new PersistingTaskRunner(); @@ -334,7 +334,7 @@ describe('GitlabOrgDiscoveryEntityProvider - refresh', () => { }); }); - // This should return all members of the SaaS Root group (group1) -> expected_full_org_scan_entities_saas + // This should return all members of the SaaS Root group (group1) and any subgroups -> expected_full_org_scan_entities_saas it('SaaS: should get all saas root group users when restrictUsersToGroup is false', async () => { const config = new ConfigReader( mock.config_org_group_restrictUsers_false_saas, diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts index 1fbd2a2a13..3eec377e73 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts @@ -359,7 +359,7 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { } let groups; - let users; + const allUsers = []; // Self-hosted: Fetch the users either from the defined group (restrictUsersToGroup) or fetch all users from the GitLab instance // SaaS: Fetch the users from the defined group (restrictUsersToGroup) or fetch all users from the root group. @@ -367,13 +367,15 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { groups = (await this.gitLabClient.listDescendantGroups(this.config.group)) .items; groups.push(await this.gitLabClient.getGroupByPath(this.config.group)); // adds the parent group for #26554 - users = paginated( - options => - this.gitLabClient.listGroupMembers(this.config.group, options), // calls /groups//members - { - page: 1, - per_page: 100, - }, + allUsers.push( + paginated( + options => + this.gitLabClient.listGroupMembers(this.config.group, options), // calls /groups//members + { + page: 1, + per_page: 100, + }, + ), ); } else if ( this.gitLabClient.isSelfManaged() && @@ -387,35 +389,46 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { all_available: true, }, ); - users = paginated( - options => this.gitLabClient.listUsers(options), // calls /users? - { page: 1, per_page: 100, active: true }, + allUsers.push( + paginated( + options => this.gitLabClient.listUsers(options), // calls /users? + { page: 1, per_page: 100, active: true }, + ), ); } // SaaS: Fetch the users from the defined group (restrictUsersToGroup) or fetch all users from the root group. else { - groups = (await this.gitLabClient.listDescendantGroups(this.config.group)) - .items; + const descendantGroups = ( + await this.gitLabClient.listDescendantGroups(this.config.group) + ).items; + groups = descendantGroups; groups.push(await this.gitLabClient.getGroupByPath(this.config.group)); // adds the parent group for #26554 const rootGroupSplit = this.config.group.split('/'); - const groupPath = this.config.restrictUsersToGroup - ? this.config.group - : rootGroupSplit[0]; - users = paginated( - options => - this.gitLabClient.listSaaSUsers( - groupPath, - options, - this.config.includeUsersWithoutSeat, + const groupPaths = this.config.restrictUsersToGroup + ? [this.config.group] + : [rootGroupSplit[0], ...descendantGroups.map(g => `${g.id}`)]; + + // Fetch users group and descendant groups + for (const group of groupPaths) { + logger.debug(`Fetching users for group: ${group}`); + allUsers.push( + paginated( + options => + this.gitLabClient.listSaaSUsers( + group, + options, + this.config.includeUsersWithoutSeat, + ), + { + page: 1, + per_page: 100, + }, ), - { - page: 1, - per_page: 100, - }, - ); + ); + } } const idMappedUser: { [userId: number]: GitLabUser } = {}; @@ -430,16 +443,23 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { matches: [], }; - for await (const user of users) { - userRes.scanned++; + for (const users of allUsers) { + // Iterate through paginated users + for await (const user of users) { + // Skip if user already processed and do not count it as scanned + if (user.id in idMappedUser) { + continue; // Skip if user already processed + } + userRes.scanned++; - if (!this.shouldProcessUser(user)) { - logger.debug(`Skipped user: ${user.username}`); - continue; + if (!this.shouldProcessUser(user)) { + logger.debug(`Skipped user: ${user.username}`); + continue; + } + + idMappedUser[user.id] = user; + userRes.matches.push(user); } - - idMappedUser[user.id] = user; - userRes.matches.push(user); } for await (const group of groups) { diff --git a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md index d2d26a09d8..1bb1971898 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md +++ b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-catalog-backend-module-incremental-ingestion +## 0.7.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@2.0.1-next.1 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-defaults@0.10.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12-next.1 + - @backstage/plugin-permission-common@0.9.0 + +## 0.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@2.0.1-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/backend-defaults@0.10.1-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + ## 0.7.0 ### Minor Changes diff --git a/plugins/catalog-backend-module-incremental-ingestion/package.json b/plugins/catalog-backend-module-incremental-ingestion/package.json index ca7462dceb..b4cb22eb36 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.0", + "version": "0.7.1-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 8fc469a957..53039b1260 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.11.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.11.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + ## 0.11.5 ### Patch Changes diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index 8adbe61d64..a5d22ba3f5 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.5", + "version": "0.11.6-next.1", "description": "A Backstage catalog backend module that helps integrate towards LDAP", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-logs/CHANGELOG.md b/plugins/catalog-backend-module-logs/CHANGELOG.md index 2173b0cd6f..5795eb8023 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.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@2.0.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@2.0.1-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + ## 0.1.10 ### Patch Changes diff --git a/plugins/catalog-backend-module-logs/package.json b/plugins/catalog-backend-module-logs/package.json index 7ee71e2888..c21f08cfa5 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.10", + "version": "0.1.11-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 7bbd41bd44..9e495277c3 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-catalog-backend-module-msgraph +## 0.7.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + ## 0.7.0 ### Minor Changes diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index 838991972a..67285b2897 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.7.0", + "version": "0.7.1-next.1", "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 55b1cf9feb..0db664e8c8 100644 --- a/plugins/catalog-backend-module-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-openapi/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-catalog-backend-module-openapi +## 0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/integration@1.17.0 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + ## 0.2.10 ### Patch Changes diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json index 27d1a097f1..742cf6e796 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.10", + "version": "0.2.11-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 232cc71951..f1c0618cc8 100644 --- a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md +++ b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-catalog-backend-module-puppetdb +## 0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + ## 0.2.10 ### Patch Changes diff --git a/plugins/catalog-backend-module-puppetdb/package.json b/plugins/catalog-backend-module-puppetdb/package.json index a8f05e149d..0df55cbbe0 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.10", + "version": "0.2.11-next.1", "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 29b0526814..1eacdb3f61 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md +++ b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-catalog-backend-module-scaffolder-entity-model +## 0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-scaffolder-common@1.5.11 + +## 0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + ## 0.2.8 ### 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 9edabd419a..2c78f3f07c 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.8", + "version": "0.2.9-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 fe1ebc939e..87e43914b0 100644 --- a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md +++ b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-catalog-backend-module-unprocessed +## 0.6.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.8 + - @backstage/plugin-permission-common@0.9.0 + +## 0.6.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + ## 0.6.0 ### Minor Changes diff --git a/plugins/catalog-backend-module-unprocessed/package.json b/plugins/catalog-backend-module-unprocessed/package.json index 7f5bd4d729..f8a7d529b1 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.0", + "version": "0.6.1-next.1", "description": "Backstage Catalog module to view unprocessed entities", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index ef73065d81..e09921bb01 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-catalog-backend +## 2.0.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-openapi-utils@0.5.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-events-node@0.4.12-next.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1-next.1 + +## 2.0.1-next.0 + +### Patch Changes + +- 4654a78: Update `refresh_state_references.id` to be a big int +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + - @backstage/plugin-permission-node@0.10.1-next.0 + - @backstage/backend-openapi-utils@0.5.4-next.0 + ## 2.0.0 ### Major Changes diff --git a/plugins/catalog-backend/migrations/20250514000000_refresh_state_references_big_increments.js b/plugins/catalog-backend/migrations/20250514000000_refresh_state_references_big_increments.js new file mode 100644 index 0000000000..780f2f34a2 --- /dev/null +++ b/plugins/catalog-backend/migrations/20250514000000_refresh_state_references_big_increments.js @@ -0,0 +1,53 @@ +/* + * 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. + */ + +/** + * @param {import('knex').Knex} knex + * @returns {Promise} + */ +exports.up = async function up(knex) { + if (knex.client.config.client.includes('pg')) { + await knex.schema.raw( + `ALTER TABLE refresh_state_references ALTER COLUMN id TYPE bigint;`, + ); + await knex.schema.raw( + `ALTER SEQUENCE refresh_state_references_id_seq AS bigint MAXVALUE 9223372036854775807;`, + ); + } else if (knex.client.config.client.includes('mysql')) { + await knex.schema.raw( + `ALTER TABLE refresh_state_references MODIFY id bigint AUTO_INCREMENT;`, + ); + } +}; + +/** + * @param {import('knex').Knex} knex + * @returns {Promise} + */ +exports.down = async function down(knex) { + if (knex.client.config.client.includes('pg')) { + await knex.schema.raw( + `ALTER SEQUENCE refresh_state_references_id_seq AS integer MAXVALUE 2147483647;`, + ); + await knex.schema.raw( + `ALTER TABLE refresh_state_references ALTER COLUMN id TYPE integer;`, + ); + } else if (knex.client.config.client.includes('mysql')) { + await knex.schema.raw( + `ALTER TABLE refresh_state_references MODIFY id integer AUTO_INCREMENT;`, + ); + } +}; diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index f761f9dde0..8547858f65 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "2.0.0", + "version": "2.0.1-next.1", "description": "The Backstage backend plugin that provides the Backstage catalog", "backstage": { "role": "backend-plugin", diff --git a/plugins/catalog-backend/report-alpha.api.md b/plugins/catalog-backend/report-alpha.api.md index ca521a568d..caa77a0d40 100644 --- a/plugins/catalog-backend/report-alpha.api.md +++ b/plugins/catalog-backend/report-alpha.api.md @@ -30,6 +30,7 @@ export const catalogConditions: Conditions<{ 'catalog-entity', { label: string; + value?: string | undefined; } >; hasMetadata: PermissionRule< @@ -103,6 +104,7 @@ export const permissionRules: { 'catalog-entity', { label: string; + value?: string | undefined; } >; hasMetadata: PermissionRule< diff --git a/plugins/catalog-backend/report.sql.md b/plugins/catalog-backend/report.sql.md index 3402b05063..4f76d2a3f5 100644 --- a/plugins/catalog-backend/report.sql.md +++ b/plugins/catalog-backend/report.sql.md @@ -8,7 +8,7 @@ ## Sequences - `location_update_log_id_seq` (bigint) -- `refresh_state_references_id_seq` (integer) +- `refresh_state_references_id_seq` (bigint) ## Table `final_entities` @@ -93,12 +93,12 @@ ## Table `refresh_state_references` -| Column | Type | Nullable | Max Length | Default | -| ------------------- | --------- | -------- | ---------- | ------------------------------------------------------ | -| `id` | `integer` | false | - | `nextval('refresh_state_references_id_seq'::regclass)` | -| `source_entity_ref` | `text` | true | - | - | -| `source_key` | `text` | true | - | - | -| `target_entity_ref` | `text` | false | - | - | +| Column | Type | Nullable | Max Length | Default | +| ------------------- | -------- | -------- | ---------- | ------------------------------------------------------ | +| `id` | `bigint` | false | - | `nextval('refresh_state_references_id_seq'::regclass)` | +| `source_entity_ref` | `text` | true | - | - | +| `source_key` | `text` | true | - | - | +| `target_entity_ref` | `text` | false | - | - | ### Indices diff --git a/plugins/catalog-backend/src/database/DefaultProviderDatabase.test.ts b/plugins/catalog-backend/src/database/DefaultProviderDatabase.test.ts index 383e06100f..d9465b27a6 100644 --- a/plugins/catalog-backend/src/database/DefaultProviderDatabase.test.ts +++ b/plugins/catalog-backend/src/database/DefaultProviderDatabase.test.ts @@ -610,16 +610,18 @@ describe('DefaultProviderDatabase', () => { ); let references = await knex( 'refresh_state_references', - ).select(); + ) + .select() + .orderBy('id'); expect(references).toEqual([ { - id: 1, + id: expect.anything(), source_key: 'lols', source_entity_ref: null, target_entity_ref: 'component:default/a', }, { - id: 2, + id: expect.anything(), source_key: 'lols', source_entity_ref: null, target_entity_ref: 'component:default/b', @@ -670,16 +672,18 @@ describe('DefaultProviderDatabase', () => { ); references = await knex( 'refresh_state_references', - ).select(); + ) + .select() + .orderBy('id'); expect(references).toEqual([ { - id: 2, + id: expect.anything(), source_key: 'lols', source_entity_ref: null, target_entity_ref: 'component:default/b', }, { - id: 3, + id: expect.anything(), source_key: 'lols', source_entity_ref: null, target_entity_ref: 'component:default/a', diff --git a/plugins/catalog-backend/src/permissions/rules/hasLabel.test.ts b/plugins/catalog-backend/src/permissions/rules/hasLabel.test.ts index 8aa43402c5..7743120eaa 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasLabel.test.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasLabel.test.ts @@ -73,6 +73,50 @@ describe('hasLabel permission rule', () => { ), ).toEqual(true); }); + + it('returns false when specified label has different than expected value', () => { + expect( + hasLabel.apply( + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test-component', + labels: { + someLabel: 'foo', + 'backstage.io/testLabel': 'bar', + }, + }, + }, + { + label: 'backstage.io/testLabel', + value: 'baz', + }, + ), + ).toEqual(false); + }); + + it('returns true when specified label has expected value', () => { + expect( + hasLabel.apply( + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test-component', + labels: { + someLabel: 'foo', + 'backstage.io/testLabel': 'bar', + }, + }, + }, + { + label: 'backstage.io/testLabel', + value: 'bar', + }, + ), + ).toEqual(true); + }); }); describe('toQuery', () => { @@ -85,5 +129,17 @@ describe('hasLabel permission rule', () => { key: 'metadata.labels.backstage.io/testlabel', }); }); + + it('returns an appropriate catalog-backend filter with values', () => { + expect( + hasLabel.toQuery({ + label: 'backstage.io/testLabel', + value: 'foo', + }), + ).toEqual({ + key: 'metadata.labels.backstage.io/testLabel', + values: ['foo'], + }); + }); }); }); diff --git a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts index e825b05b04..40a96dbf49 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts @@ -29,10 +29,18 @@ export const hasLabel = createPermissionRule({ resourceRef: catalogEntityPermissionResourceRef, paramsSchema: z.object({ label: z.string().describe('Name of the label to match on'), + value: z.string().optional().describe('Value of the label to match on'), }), - apply: (resource, { label }) => - !!resource.metadata.labels?.hasOwnProperty(label), - toQuery: ({ label }) => ({ - key: `metadata.labels.${label}`, - }), + apply: (resource, { label, value }) => + !!resource.metadata.labels?.hasOwnProperty(label) && + (value === undefined ? true : resource.metadata.labels?.[label] === value), + toQuery: ({ label, value }) => + value === undefined + ? { + key: `metadata.labels.${label}`, + } + : { + key: `metadata.labels.${label}`, + values: [value], + }, }); diff --git a/plugins/catalog-backend/src/tests/migrations.test.ts b/plugins/catalog-backend/src/tests/migrations.test.ts index f58b174f93..e2a86c5c71 100644 --- a/plugins/catalog-backend/src/tests/migrations.test.ts +++ b/plugins/catalog-backend/src/tests/migrations.test.ts @@ -521,6 +521,7 @@ describe('migrations', () => { await knex.destroy(); }, ); + it.each(databases.eachSupportedId())( '20250401200503_update_refresh_state_columns.js, %p', async databaseId => { @@ -604,4 +605,89 @@ describe('migrations', () => { await knex.destroy(); }, ); + + it.each(databases.eachSupportedId())( + '20250514000000_refresh_state_references_big_increments.js, %p', + async databaseId => { + const knex = await databases.init(databaseId); + + const read = async () => { + return await knex('refresh_state_references') + .orderBy('id') + .then(rs => rs.map(r => ({ ...r, id: String(r.id) }))); + }; + + // Run migrations up to just before the target migration + await migrateUntilBefore( + knex, + '20250514000000_refresh_state_references_big_increments.js', + ); + + await knex('refresh_state').insert({ + entity_id: 'a', + entity_ref: 'k:ns/a', + unprocessed_entity: '{}', + cache: '{}', + errors: '[]', + next_update_at: knex.fn.now(), + last_discovery_at: knex.fn.now(), + }); + await knex('refresh_state_references').insert({ + source_key: 'before', + target_entity_ref: 'k:ns/a', + }); + + await migrateUpOnce(knex); + + // can still insert with auto generated id in sequence + await knex('refresh_state_references').insert({ + source_key: 'after1', + target_entity_ref: 'k:ns/a', + }); + await expect(read()).resolves.toEqual([ + { + id: '1', + source_key: 'before', + source_entity_ref: null, + target_entity_ref: 'k:ns/a', + }, + { + id: '2', + source_key: 'after1', + source_entity_ref: null, + target_entity_ref: 'k:ns/a', + }, + ]); + + await migrateDownOnce(knex); + + // can still insert with auto generated id in sequence + await knex('refresh_state_references').insert({ + source_key: 'after2', + target_entity_ref: 'k:ns/a', + }); + await expect(read()).resolves.toEqual([ + { + id: '1', + source_key: 'before', + source_entity_ref: null, + target_entity_ref: 'k:ns/a', + }, + { + id: '2', + source_key: 'after1', + source_entity_ref: null, + target_entity_ref: 'k:ns/a', + }, + { + id: '3', + source_key: 'after2', + source_entity_ref: null, + target_entity_ref: 'k:ns/a', + }, + ]); + + await knex.destroy(); + }, + ); }); diff --git a/plugins/catalog-graph/CHANGELOG.md b/plugins/catalog-graph/CHANGELOG.md index 5c6ae5b5ec..c1be6d3076 100644 --- a/plugins/catalog-graph/CHANGELOG.md +++ b/plugins/catalog-graph/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-catalog-graph +## 0.4.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/types@1.2.1 + +## 0.4.20-next.0 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + ## 0.4.19 ### Patch Changes diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index f22ab97b9d..f7cdb8c4bc 100644 --- a/plugins/catalog-graph/package.json +++ b/plugins/catalog-graph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-graph", - "version": "0.4.19", + "version": "0.4.20-next.1", "backstage": { "role": "frontend-plugin", "pluginId": "catalog-graph", diff --git a/plugins/catalog-graph/src/alpha.tsx b/plugins/catalog-graph/src/alpha.tsx index d70232e92a..aa1f6a05cf 100644 --- a/plugins/catalog-graph/src/alpha.tsx +++ b/plugins/catalog-graph/src/alpha.tsx @@ -87,6 +87,7 @@ const CatalogGraphPage = PageBlueprint.makeWithOverrides({ export default createFrontendPlugin({ pluginId: 'catalog-graph', + info: { packageJson: () => import('../package.json') }, routes: { catalogGraph: convertLegacyRouteRef(catalogGraphRouteRef), }, diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index 375444abe9..dfb001c4d7 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/plugin-catalog-import +## 0.13.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/integration@1.17.0 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.13.1-next.0 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + - @backstage/integration-react@1.2.7 + ## 0.13.0 ### Minor Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 06cca3eb57..b20568c9f1 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.0", + "version": "0.13.1-next.1", "description": "A Backstage plugin the helps you import entities into your catalog", "backstage": { "role": "frontend-plugin", diff --git a/plugins/catalog-import/report-alpha.api.md b/plugins/catalog-import/report-alpha.api.md index d4dab4549a..229c2ff10c 100644 --- a/plugins/catalog-import/report-alpha.api.md +++ b/plugins/catalog-import/report-alpha.api.md @@ -26,6 +26,8 @@ export const catalogImportTranslationRef: TranslationRef< readonly 'importInfoCard.fileLinkTitle': 'Link to an existing entity file'; readonly 'importInfoCard.examplePrefix': 'Example: '; readonly 'importInfoCard.fileLinkDescription': 'The wizard analyzes the file, previews the entities, and adds them to the {{appTitle}} catalog.'; + readonly 'importInfoCard.exampleDescription': 'The wizard discovers all {{catalogFilename}} files in the repository, previews the entities, and adds them to the {{appTitle}} catalog.'; + readonly 'importInfoCard.preparePullRequestDescription': 'If no entities are found, the wizard will prepare a Pull Request that adds an example {{catalogFilename}} and prepares the {{appTitle}} catalog to load all entities as soon as the Pull Request is merged.'; readonly 'importInfoCard.githubIntegration.label': 'GitHub only'; readonly 'importInfoCard.githubIntegration.title': 'Link to a repository'; readonly 'importStepper.finish.title': 'Finish'; @@ -65,6 +67,7 @@ export const catalogImportTranslationRef: TranslationRef< readonly 'stepInitAnalyzeUrl.error.locations': 'There are no entities at this location'; readonly 'stepInitAnalyzeUrl.urlHelperText': 'Enter the full path to your entity file to start tracking your component'; readonly 'stepInitAnalyzeUrl.nextButtonText': 'Analyze'; + readonly 'stepPrepareCreatePullRequest.description': 'You entered a link to a {{integrationType}} repository but a {{catalogFilename}} could not be found. Use this form to open a Pull Request that creates one.'; readonly 'stepPrepareCreatePullRequest.nextButtonText': 'Create PR'; readonly 'stepPrepareCreatePullRequest.previewPr.title': 'Preview Pull Request'; readonly 'stepPrepareCreatePullRequest.previewPr.subheader': 'Create a new Pull Request'; diff --git a/plugins/catalog-import/src/alpha.tsx b/plugins/catalog-import/src/alpha.tsx index 3fade2d92b..17260acc14 100644 --- a/plugins/catalog-import/src/alpha.tsx +++ b/plugins/catalog-import/src/alpha.tsx @@ -87,6 +87,7 @@ const catalogImportApi = ApiBlueprint.make({ /** @alpha */ export default createFrontendPlugin({ pluginId: 'catalog-import', + info: { packageJson: () => import('../package.json') }, extensions: [catalogImportApi, catalogImportPage], routes: { importPage: convertLegacyRouteRef(rootRouteRef), diff --git a/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx b/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx index 1777c0786b..f9103a00cf 100644 --- a/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx +++ b/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx @@ -92,16 +92,17 @@ export const ImportInfoCard = (props: ImportInfoCardProps) => { {exampleRepositoryUrl} - The wizard discovers all {catalogFilename} files in the - repository, previews the entities, and adds them to the {appTitle}{' '} - catalog. + {t('importInfoCard.exampleDescription', { + catalogFilename: {catalogFilename}, + appTitle, + })} {catalogImportApi.preparePullRequest && ( - If no entities are found, the wizard will prepare a Pull Request - that adds an example {catalogFilename} and prepares - the {appTitle} catalog to load all entities as soon as the Pull - Request is merged. + {t('importInfoCard.preparePullRequestDescription', { + catalogFilename: {catalogFilename}, + appTitle, + })} )} diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx index a40f4868e9..e42de5257e 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx @@ -227,9 +227,10 @@ export const StepPrepareCreatePullRequest = ( return ( <> - You entered a link to a {analyzeResult.integrationType} repository but a{' '} - {catalogFilename} could not be found. Use this form to open - a Pull Request that creates one. + {t('stepPrepareCreatePullRequest.description', { + integrationType: analyzeResult.integrationType, + catalogFilename: {catalogFilename}, + })} {!prDefaultsLoading && ( diff --git a/plugins/catalog-import/src/translation.ts b/plugins/catalog-import/src/translation.ts index 857b551d9c..6fcb33f11c 100644 --- a/plugins/catalog-import/src/translation.ts +++ b/plugins/catalog-import/src/translation.ts @@ -42,6 +42,10 @@ export const catalogImportTranslationRef = createTranslationRef({ title: 'Link to a repository', label: 'GitHub only', }, + exampleDescription: + 'The wizard discovers all {{catalogFilename}} files in the repository, previews the entities, and adds them to the {{appTitle}} catalog.', + preparePullRequestDescription: + 'If no entities are found, the wizard will prepare a Pull Request that adds an example {{catalogFilename}} and prepares the {{appTitle}} catalog to load all entities as soon as the Pull Request is merged.', }, importStepper: { singleLocation: { @@ -114,6 +118,8 @@ export const catalogImportTranslationRef = createTranslationRef({ nextButtonText: 'Analyze', }, stepPrepareCreatePullRequest: { + description: + 'You entered a link to a {{integrationType}} repository but a {{catalogFilename}} could not be found. Use this form to open a Pull Request that creates one.', previewPr: { title: 'Preview Pull Request', subheader: 'Create a new Pull Request', diff --git a/plugins/catalog-node/CHANGELOG.md b/plugins/catalog-node/CHANGELOG.md index 64021e67b6..b42bb83c6f 100644 --- a/plugins/catalog-node/CHANGELOG.md +++ b/plugins/catalog-node/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-catalog-node +## 1.17.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1-next.1 + +## 1.17.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-permission-node@0.10.1-next.0 + ## 1.17.0 ### Minor Changes diff --git a/plugins/catalog-node/package.json b/plugins/catalog-node/package.json index e050020a31..bfe1bb218f 100644 --- a/plugins/catalog-node/package.json +++ b/plugins/catalog-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-node", - "version": "1.17.0", + "version": "1.17.1-next.1", "description": "The plugin-catalog-node module for @backstage/plugin-catalog-backend", "backstage": { "role": "node-library", diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index 5f0e8b8887..3caa6d7959 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-catalog-react +## 1.18.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/frontend-test-utils@0.3.3-next.0 + - @backstage/integration-react@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-react@0.4.34 + +## 1.18.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + - @backstage/frontend-test-utils@0.3.3-next.0 + - @backstage/integration-react@1.2.7 + ## 1.18.0 ### Minor Changes diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index 7201d6e73f..9f5b7d7b3c 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-react", - "version": "1.18.0", + "version": "1.18.1-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/report-alpha.api.md b/plugins/catalog-react/report-alpha.api.md index 46d3030140..36a93a8a5e 100644 --- a/plugins/catalog-react/report-alpha.api.md +++ b/plugins/catalog-react/report-alpha.api.md @@ -9,6 +9,7 @@ import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { IconLinkVerticalProps } from '@backstage/core-components'; import { JsonValue } from '@backstage/types'; import { JSX as JSX_2 } from 'react'; import { ReactNode } from 'react'; @@ -390,6 +391,64 @@ export const EntityHeaderBlueprint: ExtensionBlueprint<{ }; }>; +// @alpha (undocumented) +export const EntityIconLinkBlueprint: ExtensionBlueprint<{ + kind: 'entity-icon-link'; + name: undefined; + params: { + useProps: () => Omit; + filter?: EntityPredicate | ((entity: Entity) => boolean); + }; + output: + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + () => IconLinkVerticalProps, + 'entity-icon-link-props', + {} + >; + inputs: {}; + config: { + label: string | undefined; + title: string | undefined; + filter: EntityPredicate | undefined; + }; + configInput: { + filter?: EntityPredicate | undefined; + label?: string | undefined; + title?: string | undefined; + }; + dataRefs: { + useProps: ConfigurableExtensionDataRef< + () => IconLinkVerticalProps, + 'entity-icon-link-props', + {} + >; + filterFunction: ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + {} + >; + filterExpression: ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + {} + >; + }; +}>; + // @alpha (undocumented) export type EntityPredicate = | EntityPredicateExpression diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityIconLinkBlueprint.tsx b/plugins/catalog-react/src/alpha/blueprints/EntityIconLinkBlueprint.tsx new file mode 100644 index 0000000000..11c9a2b805 --- /dev/null +++ b/plugins/catalog-react/src/alpha/blueprints/EntityIconLinkBlueprint.tsx @@ -0,0 +1,83 @@ +/* + * 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 { IconLinkVerticalProps } from '@backstage/core-components'; +import { + createExtensionBlueprint, + createExtensionDataRef, +} from '@backstage/frontend-plugin-api'; + +import { EntityPredicate } from '../predicates'; +import { createEntityPredicateSchema } from '../predicates/createEntityPredicateSchema'; + +import { + entityFilterExpressionDataRef, + entityFilterFunctionDataRef, +} from './extensionData'; +import { Entity } from '@backstage/catalog-model'; +import { resolveEntityFilterData } from './resolveEntityFilterData'; + +const entityIconLinkPropsDataRef = createExtensionDataRef< + () => IconLinkVerticalProps +>().with({ + id: 'entity-icon-link-props', +}); + +/** @alpha */ +export const EntityIconLinkBlueprint = createExtensionBlueprint({ + kind: 'entity-icon-link', + attachTo: { id: 'entity-card:catalog/about', input: 'iconLinks' }, + output: [ + entityFilterFunctionDataRef.optional(), + entityFilterExpressionDataRef.optional(), + entityIconLinkPropsDataRef, + ], + dataRefs: { + useProps: entityIconLinkPropsDataRef, + filterFunction: entityFilterFunctionDataRef, + filterExpression: entityFilterExpressionDataRef, + }, + config: { + schema: { + label: z => z.string().optional(), + title: z => z.string().optional(), + filter: z => createEntityPredicateSchema(z).optional(), + }, + }, + *factory( + params: { + useProps: () => Omit; + filter?: EntityPredicate | ((entity: Entity) => boolean); + }, + { config, node }, + ) { + const { filter, useProps } = params; + yield* resolveEntityFilterData(filter, config, node); + // Only include properties that are defined in the config + // to avoid overriding defaults with undefined values + const configProps = Object.entries(config).reduce( + (rest, [key, value]) => + value !== undefined + ? { + ...rest, + [key]: value, + } + : rest, + {}, + ); + yield entityIconLinkPropsDataRef(() => ({ ...useProps(), ...configProps })); + }, +}); diff --git a/plugins/catalog-react/src/alpha/blueprints/index.ts b/plugins/catalog-react/src/alpha/blueprints/index.ts index b21522331d..640ebb881c 100644 --- a/plugins/catalog-react/src/alpha/blueprints/index.ts +++ b/plugins/catalog-react/src/alpha/blueprints/index.ts @@ -28,3 +28,4 @@ export { type EntityContextMenuItemParams, type UseProps, } from './EntityContextMenuItemBlueprint'; +export { EntityIconLinkBlueprint } from './EntityIconLinkBlueprint'; diff --git a/plugins/catalog-unprocessed-entities/CHANGELOG.md b/plugins/catalog-unprocessed-entities/CHANGELOG.md index c250f000cb..cc82f793d5 100644 --- a/plugins/catalog-unprocessed-entities/CHANGELOG.md +++ b/plugins/catalog-unprocessed-entities/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-catalog-unprocessed-entities +## 0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + +## 0.2.18-next.0 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + ## 0.2.17 ### Patch Changes diff --git a/plugins/catalog-unprocessed-entities/package.json b/plugins/catalog-unprocessed-entities/package.json index 9a1e9c5688..95ef6bc065 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.17", + "version": "0.2.18-next.1", "backstage": { "role": "frontend-plugin", "pluginId": "catalog-unprocessed-entities", diff --git a/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx b/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx index 8ff83605c7..34a40b23d7 100644 --- a/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx +++ b/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx @@ -74,6 +74,7 @@ export const catalogUnprocessedEntitiesNavItem = NavItemBlueprint.make({ /** @alpha */ export default createFrontendPlugin({ pluginId: 'catalog-unprocessed-entities', + info: { packageJson: () => import('../../package.json') }, routes: { root: convertLegacyRouteRef(rootRouteRef), }, diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 4f1767a10b..d5f981432c 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,47 @@ # @backstage/plugin-catalog +## 1.31.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/integration-react@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/plugin-scaffolder-common@1.5.11 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-search-react@1.9.1-next.0 + - @backstage/plugin-techdocs-common@0.1.1-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + +## 1.31.0-next.0 + +### Minor Changes + +- ec7b35d: Introduced `backstage.io/techdocs-entity-path` annotation which allows deep linking into another entities TechDocs in conjunction with `backstage.io/techdocs-entity`. + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + - @backstage/plugin-techdocs-common@0.1.1-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + - @backstage/plugin-search-react@1.9.1-next.0 + - @backstage/integration-react@1.2.7 + ## 1.30.0 ### Minor Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 5ef3748e38..1e78d40306 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "1.30.0", + "version": "1.31.0-next.1", "description": "The Backstage plugin for browsing the Backstage catalog", "backstage": { "role": "frontend-plugin", @@ -72,6 +72,8 @@ "@backstage/plugin-scaffolder-common": "workspace:^", "@backstage/plugin-search-common": "workspace:^", "@backstage/plugin-search-react": "workspace:^", + "@backstage/plugin-techdocs-common": "workspace:^", + "@backstage/plugin-techdocs-react": "workspace:^", "@backstage/types": "workspace:^", "@backstage/version-bridge": "workspace:^", "@material-ui/core": "^4.12.2", diff --git a/plugins/catalog/report-alpha.api.md b/plugins/catalog/report-alpha.api.md index 7ef20d8228..4be914a69d 100644 --- a/plugins/catalog/report-alpha.api.md +++ b/plugins/catalog/report-alpha.api.md @@ -18,6 +18,7 @@ import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; +import { IconLinkVerticalProps } from '@backstage/core-components'; import { JSX as JSX_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; import { SearchResultItemExtensionComponent } from '@backstage/plugin-search-react/alpha'; @@ -336,8 +337,6 @@ const _default: FrontendPlugin< }; }>; 'entity-card:catalog/about': ExtensionDefinition<{ - kind: 'entity-card'; - name: 'about'; config: { filter: EntityPredicate | undefined; type: 'content' | 'summary' | 'info' | undefined; @@ -369,7 +368,35 @@ const _default: FrontendPlugin< optional: true; } >; - inputs: {}; + inputs: { + iconLinks: ExtensionInput< + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + () => IconLinkVerticalProps, + 'entity-icon-link-props', + {} + >, + { + singleton: false; + optional: false; + } + >; + }; + kind: 'entity-card'; + name: 'about'; params: { loader: () => Promise; filter?: string | EntityPredicate | ((entity: Entity) => boolean); @@ -923,6 +950,45 @@ const _default: FrontendPlugin< inputs: {}; params: EntityContextMenuItemParams; }>; + 'entity-icon-link:catalog/view-source': ExtensionDefinition<{ + kind: 'entity-icon-link'; + name: 'view-source'; + config: { + label: string | undefined; + title: string | undefined; + filter: EntityPredicate | undefined; + }; + configInput: { + filter?: EntityPredicate | undefined; + label?: string | undefined; + title?: string | undefined; + }; + output: + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + () => IconLinkVerticalProps, + 'entity-icon-link-props', + {} + >; + inputs: {}; + params: { + useProps: () => Omit; + filter?: EntityPredicate | ((entity: Entity) => boolean); + }; + }>; 'nav-item:catalog': ExtensionDefinition<{ kind: 'nav-item'; name: undefined; diff --git a/plugins/catalog/report.api.md b/plugins/catalog/report.api.md index 10e120d208..52b13e1c43 100644 --- a/plugins/catalog/report.api.md +++ b/plugins/catalog/report.api.md @@ -41,10 +41,9 @@ import { TabProps } from '@material-ui/core/Tab'; import { UserListFilterKind } from '@backstage/plugin-catalog-react'; // @public -export interface AboutCardProps { - // (undocumented) +export type AboutCardProps = { variant?: InfoCardVariants; -} +}; // @public (undocumented) export function AboutContent(props: AboutContentProps): JSX_2.Element; diff --git a/plugins/catalog/src/alpha/entityCards.tsx b/plugins/catalog/src/alpha/entityCards.tsx index 8662518d30..3913f8b2b7 100644 --- a/plugins/catalog/src/alpha/entityCards.tsx +++ b/plugins/catalog/src/alpha/entityCards.tsx @@ -14,17 +14,57 @@ * limitations under the License. */ -import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha'; +import { + EntityIconLinkBlueprint, + EntityCardBlueprint, +} from '@backstage/plugin-catalog-react/alpha'; import { compatWrapper } from '@backstage/core-compat-api'; +import { createExtensionInput } from '@backstage/frontend-plugin-api'; +import { + HeaderIconLinkRow, + IconLinkVerticalProps, +} from '@backstage/core-components'; +import { useEntity } from '@backstage/plugin-catalog-react'; +import { buildFilterFn } from './filter/FilterWrapper'; -export const catalogAboutEntityCard = EntityCardBlueprint.make({ +export const catalogAboutEntityCard = EntityCardBlueprint.makeWithOverrides({ name: 'about', - params: { - type: 'info', - loader: async () => - import('../components/AboutCard').then(m => - compatWrapper(), - ), + inputs: { + iconLinks: createExtensionInput([ + EntityIconLinkBlueprint.dataRefs.filterFunction.optional(), + EntityIconLinkBlueprint.dataRefs.filterExpression.optional(), + EntityIconLinkBlueprint.dataRefs.useProps, + ]), + }, + factory(originalFactory, { inputs }) { + function Subheader() { + const { entity } = useEntity(); + // The "useProps" functions may be calling other hooks, so we need to + // call them in a component function to avoid breaking the rules of hooks. + const links = inputs.iconLinks.reduce((rest, iconLink) => { + const props = iconLink.get(EntityIconLinkBlueprint.dataRefs.useProps)(); + const filter = buildFilterFn( + iconLink.get(EntityIconLinkBlueprint.dataRefs.filterFunction), + iconLink.get(EntityIconLinkBlueprint.dataRefs.filterExpression), + ); + if (filter(entity)) { + return [...rest, props]; + } + return rest; + }, new Array()); + return links.length ? : null; + } + return originalFactory({ + type: 'info', + async loader() { + const { InternalAboutCard } = await import( + '../components/AboutCard/AboutCard' + ); + return compatWrapper( + } />, + ); + }, + }); }, }); diff --git a/plugins/catalog/src/alpha/entityIconLinks.tsx b/plugins/catalog/src/alpha/entityIconLinks.tsx new file mode 100644 index 0000000000..b13b13477c --- /dev/null +++ b/plugins/catalog/src/alpha/entityIconLinks.tsx @@ -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 { EntityIconLinkBlueprint } from '@backstage/plugin-catalog-react/alpha'; +import { useCatalogSourceIconLinkProps } from '../components/AboutCard/AboutCard'; + +const catalogViewSourceEntityIconLink = EntityIconLinkBlueprint.make({ + name: 'view-source', + params: { + useProps: useCatalogSourceIconLinkProps, + }, +}); + +export default [catalogViewSourceEntityIconLink]; diff --git a/plugins/catalog/src/alpha/plugin.tsx b/plugins/catalog/src/alpha/plugin.tsx index 6fd171ed54..ff26d20003 100644 --- a/plugins/catalog/src/alpha/plugin.tsx +++ b/plugins/catalog/src/alpha/plugin.tsx @@ -33,12 +33,14 @@ import filters from './filters'; import navItems from './navItems'; import entityCards from './entityCards'; import entityContents from './entityContents'; +import entityIconLinks from './entityIconLinks'; import searchResultItems from './searchResultItems'; import contextMenuItems from './contextMenuItems'; /** @alpha */ export default createFrontendPlugin({ pluginId: 'catalog', + info: { packageJson: () => import('../../package.json') }, routes: convertLegacyRouteRefs({ catalogIndex: rootRouteRef, catalogEntity: entityRouteRef, @@ -56,6 +58,7 @@ export default createFrontendPlugin({ ...navItems, ...entityCards, ...entityContents, + ...entityIconLinks, ...contextMenuItems, ...searchResultItems, ], diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx index 0255339238..f8bebcf037 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx @@ -589,6 +589,64 @@ describe('', () => { ); }); + it('renders techdocs link to specific path', async () => { + const entity = { + apiVersion: 'v1', + kind: 'Component', + metadata: { + name: 'software', + annotations: { + 'backstage.io/techdocs-entity': 'system:default/example', + 'backstage.io/techdocs-entity-path': '/path/to/component', + }, + }, + spec: { + owner: 'guest', + type: 'service', + lifecycle: 'production', + }, + }; + + await renderInTestApp( + + + + + , + { + mountedRoutes: { + '/docs/:namespace/:kind/:name': viewTechDocRouteRef, + '/catalog/:namespace/:kind/:name': entityRouteRef, + }, + }, + ); + + expect(screen.getByText('View TechDocs').closest('a')).toHaveAttribute( + 'href', + '/docs/default/system/example/path/to/component', + ); + }); + it('renders techdocs link', async () => { const entity = { apiVersion: 'v1', diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx index bf5be95a08..fd1c05ba5f 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx @@ -13,20 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - ANNOTATION_EDIT_URL, - ANNOTATION_LOCATION, - CompoundEntityRef, - DEFAULT_NAMESPACE, - stringifyEntityRef, - parseEntityRef, -} from '@backstage/catalog-model'; + +import { useCallback } from 'react'; + +import { makeStyles } from '@material-ui/core/styles'; import Card from '@material-ui/core/Card'; import CardContent from '@material-ui/core/CardContent'; import CardHeader from '@material-ui/core/CardHeader'; import Divider from '@material-ui/core/Divider'; import IconButton from '@material-ui/core/IconButton'; -import { makeStyles } from '@material-ui/core/styles'; +import CachedIcon from '@material-ui/icons/Cached'; +import EditIcon from '@material-ui/icons/Edit'; +import DocsIcon from '@material-ui/icons/Description'; +import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; + import { AppIcon, HeaderIconLinkRow, @@ -34,42 +34,123 @@ import { InfoCardVariants, Link, } from '@backstage/core-components'; -import { useCallback } from 'react'; +import { + alertApiRef, + errorApiRef, + useApp, + useApi, + useRouteRef, +} from '@backstage/core-plugin-api'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; + import { ScmIntegrationIcon, scmIntegrationsApiRef, } from '@backstage/integration-react'; + import { - alertApiRef, - errorApiRef, - useApi, - useApp, - useRouteRef, -} from '@backstage/core-plugin-api'; + DEFAULT_NAMESPACE, + ANNOTATION_EDIT_URL, + ANNOTATION_LOCATION, + stringifyEntityRef, +} from '@backstage/catalog-model'; import { catalogApiRef, getEntitySourceLocation, useEntity, } from '@backstage/plugin-catalog-react'; -import { createFromTemplateRouteRef, viewTechDocRouteRef } from '../../routes'; - -import { AboutContent } from './AboutContent'; -import CachedIcon from '@material-ui/icons/Cached'; -import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; -import DocsIcon from '@material-ui/icons/Description'; -import EditIcon from '@material-ui/icons/Edit'; -import { isTemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { useEntityPermission } from '@backstage/plugin-catalog-react/alpha'; import { catalogEntityRefreshPermission } from '@backstage/plugin-catalog-common/alpha'; -import { useSourceTemplateCompoundEntityRef } from './hooks'; + +import { + TECHDOCS_ANNOTATION, + TECHDOCS_EXTERNAL_ANNOTATION, +} from '@backstage/plugin-techdocs-common'; +import { buildTechDocsURL } from '@backstage/plugin-techdocs-react'; + +import { isTemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { taskCreatePermission } from '@backstage/plugin-scaffolder-common/alpha'; + import { usePermission } from '@backstage/plugin-permission-react'; + +import { createFromTemplateRouteRef, viewTechDocRouteRef } from '../../routes'; import { catalogTranslationRef } from '../../alpha/translation'; -import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { useSourceTemplateCompoundEntityRef } from './hooks'; +import { AboutContent } from './AboutContent'; -const TECHDOCS_ANNOTATION = 'backstage.io/techdocs-ref'; +export function useCatalogSourceIconLinkProps() { + const { entity } = useEntity(); + const scmIntegrationsApi = useApi(scmIntegrationsApiRef); + const { t } = useTranslationRef(catalogTranslationRef); + const entitySourceLocation = getEntitySourceLocation( + entity, + scmIntegrationsApi, + ); + return { + label: t('aboutCard.viewSource'), + disabled: !entitySourceLocation, + icon: , + href: entitySourceLocation?.locationTargetUrl, + }; +} -const TECHDOCS_EXTERNAL_ANNOTATION = 'backstage.io/techdocs-entity'; +// TODO: This hook is duplicated from the TechDocs plugin for backwards compatibility +// Remove it when the the legacy frontend system support is dropped. +function useTechdocsReaderIconLinkProps(): IconLinkVerticalProps { + const { entity } = useEntity(); + const viewTechdocLink = useRouteRef(viewTechDocRouteRef); + const { t } = useTranslationRef(catalogTranslationRef); + + return { + label: t('aboutCard.viewTechdocs'), + disabled: + !( + entity.metadata.annotations?.[TECHDOCS_ANNOTATION] || + entity.metadata.annotations?.[TECHDOCS_EXTERNAL_ANNOTATION] + ) || !viewTechdocLink, + icon: , + href: buildTechDocsURL(entity, viewTechdocLink), + }; +} + +// TODO: This hook is duplicated from the Scaffolder plugin for backwards compatibility +// Remove it when the the legacy frontend system support is dropped. +function useScaffolderTemplateIconLinkProps(): IconLinkVerticalProps { + const app = useApp(); + const { entity } = useEntity(); + const templateRoute = useRouteRef(createFromTemplateRouteRef); + const { t } = useTranslationRef(catalogTranslationRef); + const Icon = app.getSystemIcon('scaffolder') ?? CreateComponentIcon; + const { allowed: canCreateTemplateTask } = usePermission({ + permission: taskCreatePermission, + }); + + return { + label: t('aboutCard.launchTemplate'), + icon: , + disabled: !templateRoute || !canCreateTemplateTask, + href: + templateRoute && + templateRoute({ + templateName: entity.metadata.name, + namespace: entity.metadata.namespace || DEFAULT_NAMESPACE, + }), + }; +} + +function DefaultAboutCardSubheader() { + const { entity } = useEntity(); + const catalogSourceIconLink = useCatalogSourceIconLinkProps(); + const techdocsreaderIconLink = useTechdocsReaderIconLinkProps(); + const scaffolderTemplateIconLink = useScaffolderTemplateIconLinkProps(); + + const links = [catalogSourceIconLink, techdocsreaderIconLink]; + if (isTemplateEntityV1beta3(entity)) { + links.push(scaffolderTemplateIconLink); + } + + return ; +} const useStyles = makeStyles({ gridItemCard: { @@ -96,27 +177,21 @@ const useStyles = makeStyles({ * * @public */ -export interface AboutCardProps { +export type AboutCardProps = { variant?: InfoCardVariants; +}; + +export interface InternalAboutCardProps extends AboutCardProps { + subheader?: JSX.Element; } -/** - * Exported publicly via the EntityAboutCard - * - * NOTE: We generally do not accept pull requests to extend this class with more - * props and customizability. If you need to tweak it, consider making a bespoke - * card in your own repository instead, that is perfect for your own needs. - */ -export function AboutCard(props: AboutCardProps) { - const { variant } = props; - const app = useApp(); +export function InternalAboutCard(props: InternalAboutCardProps) { + const { variant, subheader } = props; const classes = useStyles(); const { entity } = useEntity(); - const scmIntegrationsApi = useApi(scmIntegrationsApiRef); const catalogApi = useApi(catalogApiRef); const alertApi = useApi(alertApiRef); const errorApi = useApi(errorApiRef); - const viewTechdocLink = useRouteRef(viewTechDocRouteRef); const templateRoute = useRouteRef(createFromTemplateRouteRef); const sourceTemplateRef = useSourceTemplateCompoundEntityRef(entity); const { allowed: canRefresh } = useEntityPermission( @@ -124,79 +199,9 @@ export function AboutCard(props: AboutCardProps) { ); const { t } = useTranslationRef(catalogTranslationRef); - const { allowed: canCreateTemplateTask } = usePermission({ - permission: taskCreatePermission, - }); - - const entitySourceLocation = getEntitySourceLocation( - entity, - scmIntegrationsApi, - ); const entityMetadataEditUrl = entity.metadata.annotations?.[ANNOTATION_EDIT_URL]; - let techdocsRef: CompoundEntityRef | undefined; - - if (entity.metadata.annotations?.[TECHDOCS_EXTERNAL_ANNOTATION]) { - try { - techdocsRef = parseEntityRef( - entity.metadata.annotations?.[TECHDOCS_EXTERNAL_ANNOTATION], - ); - // not a fan of this but we don't care if the parseEntityRef fails - } catch { - techdocsRef = undefined; - } - } - - const viewInSource: IconLinkVerticalProps = { - label: t('aboutCard.viewSource'), - disabled: !entitySourceLocation, - icon: , - href: entitySourceLocation?.locationTargetUrl, - }; - const viewInTechDocs: IconLinkVerticalProps = { - label: t('aboutCard.viewTechdocs'), - disabled: - !( - entity.metadata.annotations?.[TECHDOCS_ANNOTATION] || - entity.metadata.annotations?.[TECHDOCS_EXTERNAL_ANNOTATION] - ) || !viewTechdocLink, - icon: , - href: - viewTechdocLink && - (techdocsRef - ? viewTechdocLink({ - namespace: techdocsRef.namespace || DEFAULT_NAMESPACE, - kind: techdocsRef.kind, - name: techdocsRef.name, - }) - : viewTechdocLink({ - namespace: entity.metadata.namespace || DEFAULT_NAMESPACE, - kind: entity.kind, - name: entity.metadata.name, - })), - }; - - const subHeaderLinks = [viewInSource, viewInTechDocs]; - - if (isTemplateEntityV1beta3(entity)) { - const Icon = app.getSystemIcon('scaffolder') ?? CreateComponentIcon; - - const launchTemplate: IconLinkVerticalProps = { - label: t('aboutCard.launchTemplate'), - icon: , - disabled: !templateRoute || !canCreateTemplateTask, - href: - templateRoute && - templateRoute({ - templateName: entity.metadata.name, - namespace: entity.metadata.namespace || DEFAULT_NAMESPACE, - }), - }; - - subHeaderLinks.push(launchTemplate); - } - let cardClass = ''; if (variant === 'gridItem') { cardClass = classes.gridItemCard; @@ -266,7 +271,7 @@ export function AboutCard(props: AboutCardProps) { )} } - subheader={} + subheader={subheader ?? } /> @@ -275,3 +280,14 @@ export function AboutCard(props: AboutCardProps) { ); } + +/** + * Exported publicly via the EntityAboutCard + * + * NOTE: We generally do not accept pull requests to extend this class with more + * props and customizability. If you need to tweak it, consider making a bespoke + * card in your own repository instead, that is perfect for your own needs. + */ +export function AboutCard(props: AboutCardProps) { + return ; +} diff --git a/plugins/devtools-backend/CHANGELOG.md b/plugins/devtools-backend/CHANGELOG.md index 732c22ecf9..0a11e19ddb 100644 --- a/plugins/devtools-backend/CHANGELOG.md +++ b/plugins/devtools-backend/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-devtools-backend +## 0.5.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/cli-common@0.1.15 + - @backstage/config@1.3.2 + - @backstage/config-loader@1.10.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-devtools-common@0.1.16 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1-next.1 + +## 0.5.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/backend-defaults@0.10.1-next.0 + - @backstage/config-loader@1.10.1 + - @backstage/plugin-permission-node@0.10.1-next.0 + ## 0.5.5 ### Patch Changes diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json index b8137d425c..0547b8bc9a 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.5", + "version": "0.5.6-next.1", "backstage": { "role": "backend-plugin", "pluginId": "devtools", diff --git a/plugins/devtools/CHANGELOG.md b/plugins/devtools/CHANGELOG.md index 959b7728cb..7ae5262c69 100644 --- a/plugins/devtools/CHANGELOG.md +++ b/plugins/devtools/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-devtools +## 0.1.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/plugin-devtools-common@0.1.16 + - @backstage/plugin-permission-react@0.4.34 + +## 0.1.28-next.0 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + ## 0.1.27 ### Patch Changes diff --git a/plugins/devtools/package.json b/plugins/devtools/package.json index ecc9f00e12..32ac310d53 100644 --- a/plugins/devtools/package.json +++ b/plugins/devtools/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools", - "version": "0.1.27", + "version": "0.1.28-next.1", "backstage": { "role": "frontend-plugin", "pluginId": "devtools", diff --git a/plugins/devtools/src/alpha/plugin.tsx b/plugins/devtools/src/alpha/plugin.tsx index 959670f3a5..52caaf4786 100644 --- a/plugins/devtools/src/alpha/plugin.tsx +++ b/plugins/devtools/src/alpha/plugin.tsx @@ -71,6 +71,7 @@ export const devToolsNavItem = NavItemBlueprint.make({ /** @alpha */ export default createFrontendPlugin({ pluginId: 'devtools', + info: { packageJson: () => import('../../package.json') }, routes: { root: convertLegacyRouteRef(rootRouteRef), }, diff --git a/plugins/events-backend-module-aws-sqs/CHANGELOG.md b/plugins/events-backend-module-aws-sqs/CHANGELOG.md index 5fe9e90a21..6372fb6b46 100644 --- a/plugins/events-backend-module-aws-sqs/CHANGELOG.md +++ b/plugins/events-backend-module-aws-sqs/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-events-backend-module-aws-sqs +## 0.4.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 0.4.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + ## 0.4.11 ### Patch Changes diff --git a/plugins/events-backend-module-aws-sqs/package.json b/plugins/events-backend-module-aws-sqs/package.json index 72e17efec6..7cd59759cc 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.11", + "version": "0.4.12-next.1", "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 229a3e7234..791a72a413 100644 --- a/plugins/events-backend-module-azure/CHANGELOG.md +++ b/plugins/events-backend-module-azure/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-events-backend-module-azure +## 0.2.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 0.2.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + ## 0.2.20 ### Patch Changes diff --git a/plugins/events-backend-module-azure/package.json b/plugins/events-backend-module-azure/package.json index 3916fc2d19..3ccc5436db 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.20", + "version": "0.2.21-next.1", "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 ba76ddb427..c56b358dfa 100644 --- a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-events-backend-module-bitbucket-cloud +## 0.2.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 0.2.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + ## 0.2.20 ### Patch Changes diff --git a/plugins/events-backend-module-bitbucket-cloud/package.json b/plugins/events-backend-module-bitbucket-cloud/package.json index 2f00f31c00..68d6823859 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.20", + "version": "0.2.21-next.1", "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 db61340692..7e285f28f8 100644 --- a/plugins/events-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/events-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-events-backend-module-bitbucket-server +## 0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + ## 0.1.1 ### Patch Changes diff --git a/plugins/events-backend-module-bitbucket-server/package.json b/plugins/events-backend-module-bitbucket-server/package.json index 639664e206..501088fd74 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.1", + "version": "0.1.2-next.1", "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 94d5a9d7ff..b0e7a64255 100644 --- a/plugins/events-backend-module-gerrit/CHANGELOG.md +++ b/plugins/events-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-events-backend-module-gerrit +## 0.2.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 0.2.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + ## 0.2.20 ### Patch Changes diff --git a/plugins/events-backend-module-gerrit/package.json b/plugins/events-backend-module-gerrit/package.json index a2b2b474f7..33dfe31b19 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.20", + "version": "0.2.21-next.1", "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 520affb30a..1521413713 100644 --- a/plugins/events-backend-module-github/CHANGELOG.md +++ b/plugins/events-backend-module-github/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-events-backend-module-github +## 0.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/integration@1.17.0 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + ## 0.4.0 ### Minor Changes diff --git a/plugins/events-backend-module-github/package.json b/plugins/events-backend-module-github/package.json index 3bb69f23e9..ebfefa8965 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.0", + "version": "0.4.1-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 b5a2b5ec02..7941142c71 100644 --- a/plugins/events-backend-module-gitlab/CHANGELOG.md +++ b/plugins/events-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-events-backend-module-gitlab +## 0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 0.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + ## 0.3.1 ### Patch Changes diff --git a/plugins/events-backend-module-gitlab/package.json b/plugins/events-backend-module-gitlab/package.json index 8bebe71284..23b90b6832 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.1", + "version": "0.3.2-next.1", "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 c70a42d9aa..aee8ff1327 100644 --- a/plugins/events-backend-module-google-pubsub/CHANGELOG.md +++ b/plugins/events-backend-module-google-pubsub/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-events-backend-module-google-pubsub +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + ## 0.1.0 ### Minor Changes diff --git a/plugins/events-backend-module-google-pubsub/package.json b/plugins/events-backend-module-google-pubsub/package.json index 84dfc36a7b..591067de48 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.0", + "version": "0.1.1-next.1", "description": "The google-pubsub 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 91dce5c7e8..0acf569d83 100644 --- a/plugins/events-backend-test-utils/CHANGELOG.md +++ b/plugins/events-backend-test-utils/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-events-backend-test-utils +## 0.1.45-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.12-next.1 + +## 0.1.45-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.12-next.0 + ## 0.1.44 ### Patch Changes diff --git a/plugins/events-backend-test-utils/package.json b/plugins/events-backend-test-utils/package.json index 0c0d011b5a..1662f4f943 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.44", + "version": "0.1.45-next.1", "description": "The plugin-events-backend-test-utils for @backstage/plugin-events-node", "backstage": { "role": "node-library", diff --git a/plugins/events-backend/CHANGELOG.md b/plugins/events-backend/CHANGELOG.md index b0454ed436..90f5f6009f 100644 --- a/plugins/events-backend/CHANGELOG.md +++ b/plugins/events-backend/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-events-backend +## 0.5.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.5.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 0.5.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + - @backstage/backend-openapi-utils@0.5.4-next.0 + ## 0.5.2 ### Patch Changes diff --git a/plugins/events-backend/package.json b/plugins/events-backend/package.json index 6a1b373f97..0f57e0da5a 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.2", + "version": "0.5.3-next.1", "backstage": { "role": "backend-plugin", "pluginId": "events", diff --git a/plugins/events-node/CHANGELOG.md b/plugins/events-node/CHANGELOG.md index 22b912bbe6..06960faf33 100644 --- a/plugins/events-node/CHANGELOG.md +++ b/plugins/events-node/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-events-node +## 0.4.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.4.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + ## 0.4.11 ### Patch Changes diff --git a/plugins/events-node/package.json b/plugins/events-node/package.json index 5cbae144bc..c60bb7ded1 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.11", + "version": "0.4.12-next.1", "description": "The plugin-events-node module for @backstage/plugin-events-backend", "backstage": { "role": "node-library", diff --git a/plugins/example-todo-list-backend/CHANGELOG.md b/plugins/example-todo-list-backend/CHANGELOG.md index a07ffb28bf..eb0f46dc95 100644 --- a/plugins/example-todo-list-backend/CHANGELOG.md +++ b/plugins/example-todo-list-backend/CHANGELOG.md @@ -1,5 +1,20 @@ # @internal/plugin-todo-list-backend +## 1.0.40-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/errors@1.2.7 + +## 1.0.40-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + ## 1.0.39 ### Patch Changes diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json index 95c96adc5c..1e8152f632 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.39", + "version": "1.0.40-next.1", "backstage": { "role": "backend-plugin", "pluginId": "todo-list", diff --git a/plugins/gateway-backend/CHANGELOG.md b/plugins/gateway-backend/CHANGELOG.md index e4cb5a2cd0..5521af45d0 100644 --- a/plugins/gateway-backend/CHANGELOG.md +++ b/plugins/gateway-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-gateway-backend +## 1.0.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 1.0.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + ## 1.0.1 ### Patch Changes diff --git a/plugins/gateway-backend/package.json b/plugins/gateway-backend/package.json index bc41b35f2b..c7efdbfaec 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.1", + "version": "1.0.2-next.1", "backstage": { "role": "backend-plugin", "pluginId": "gateway", diff --git a/plugins/home/CHANGELOG.md b/plugins/home/CHANGELOG.md index 9a67d64ed3..3b4972c087 100644 --- a/plugins/home/CHANGELOG.md +++ b/plugins/home/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-home +## 0.8.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-app-api@1.17.0 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/theme@0.6.6 + - @backstage/plugin-home-react@0.1.26 + +## 0.8.9-next.0 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + ## 0.8.8 ### Patch Changes diff --git a/plugins/home/package.json b/plugins/home/package.json index a1e9af6d0e..e6550137fc 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home", - "version": "0.8.8", + "version": "0.8.9-next.1", "description": "A Backstage plugin that helps you build a home page", "backstage": { "role": "frontend-plugin", diff --git a/plugins/home/src/alpha.tsx b/plugins/home/src/alpha.tsx index 04d612ef1c..854a8a7dd3 100644 --- a/plugins/home/src/alpha.tsx +++ b/plugins/home/src/alpha.tsx @@ -68,6 +68,7 @@ const homePage = PageBlueprint.makeWithOverrides({ */ export default createFrontendPlugin({ pluginId: 'home', + info: { packageJson: () => import('../package.json') }, extensions: [homePage], routes: { root: rootRouteRef, diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index ab3e338be8..eab166f2b5 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-kubernetes-backend +## 0.19.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration-aws-node@0.1.16 + - @backstage/types@1.2.1 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/plugin-kubernetes-node@0.3.1-next.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1-next.1 + +## 0.19.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-kubernetes-node@0.3.1-next.0 + - @backstage/plugin-permission-node@0.10.1-next.0 + ## 0.19.6 ### Patch Changes diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index 7de68ba631..bbd01f2c45 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-backend", - "version": "0.19.6", + "version": "0.19.7-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 e1eb2dbefa..bb7a443367 100644 --- a/plugins/kubernetes-cluster/CHANGELOG.md +++ b/plugins/kubernetes-cluster/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-kubernetes-cluster +## 0.0.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/plugin-kubernetes-react@0.5.7 + - @backstage/plugin-permission-react@0.4.34 + +## 0.0.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.18.1-next.0 + ## 0.0.25 ### Patch Changes diff --git a/plugins/kubernetes-cluster/package.json b/plugins/kubernetes-cluster/package.json index df16a4423b..bb31ebeceb 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.25", + "version": "0.0.26-next.1", "description": "A Backstage plugin that shows details of Kubernetes clusters", "backstage": { "role": "frontend-plugin", diff --git a/plugins/kubernetes-node/CHANGELOG.md b/plugins/kubernetes-node/CHANGELOG.md index aba2067225..887629d69a 100644 --- a/plugins/kubernetes-node/CHANGELOG.md +++ b/plugins/kubernetes-node/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-kubernetes-node +## 0.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/types@1.2.1 + - @backstage/plugin-kubernetes-common@0.9.5 + +## 0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + ## 0.3.0 ### Minor Changes diff --git a/plugins/kubernetes-node/package.json b/plugins/kubernetes-node/package.json index f6ecd27dee..598c3e4a7f 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.0", + "version": "0.3.1-next.1", "description": "Node.js library for the kubernetes plugin", "backstage": { "role": "node-library", diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index 6118cb2c9d..4d6678299d 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-kubernetes +## 0.12.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/plugin-kubernetes-common@0.9.5 + - @backstage/plugin-kubernetes-react@0.5.7 + - @backstage/plugin-permission-react@0.4.34 + +## 0.12.8-next.0 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + ## 0.12.7 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 39992740fb..d0fe8bc9d9 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes", - "version": "0.12.7", + "version": "0.12.8-next.1", "description": "A Backstage plugin that integrates towards Kubernetes", "backstage": { "role": "frontend-plugin", diff --git a/plugins/kubernetes/src/alpha/plugin.tsx b/plugins/kubernetes/src/alpha/plugin.tsx index 35cb9e037d..db7b2439bf 100644 --- a/plugins/kubernetes/src/alpha/plugin.tsx +++ b/plugins/kubernetes/src/alpha/plugin.tsx @@ -28,6 +28,7 @@ import { export default createFrontendPlugin({ pluginId: 'kubernetes', + info: { packageJson: () => import('../../package.json') }, extensions: [ kubernetesPage, entityKubernetesContent, diff --git a/plugins/notifications-backend-module-email/CHANGELOG.md b/plugins/notifications-backend-module-email/CHANGELOG.md index f4156a8038..32bb0891e4 100644 --- a/plugins/notifications-backend-module-email/CHANGELOG.md +++ b/plugins/notifications-backend-module-email/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-notifications-backend-module-email +## 0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-notifications-common@0.0.9-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/plugin-notifications-node@0.2.16-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/integration-aws-node@0.1.16 + - @backstage/types@1.2.1 + +## 0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-notifications-node@0.2.16-next.0 + ## 0.3.9 ### Patch Changes diff --git a/plugins/notifications-backend-module-email/package.json b/plugins/notifications-backend-module-email/package.json index 8f5939e0e2..b841dafc2e 100644 --- a/plugins/notifications-backend-module-email/package.json +++ b/plugins/notifications-backend-module-email/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend-module-email", - "version": "0.3.9", + "version": "0.3.10-next.1", "description": "The email backend module for the notifications plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/notifications-backend-module-email/src/module.ts b/plugins/notifications-backend-module-email/src/module.ts index c83d27b6a3..0ee7e811b4 100644 --- a/plugins/notifications-backend-module-email/src/module.ts +++ b/plugins/notifications-backend-module-email/src/module.ts @@ -13,11 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { coreServices, createBackendModule, } from '@backstage/backend-plugin-api'; -import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha'; +import { catalogServiceRef } from '@backstage/plugin-catalog-node'; import { notificationsProcessingExtensionPoint } from '@backstage/plugin-notifications-node'; import { NotificationsEmailProcessor } from './processor'; import { diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts index d165973424..3496d431e3 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts @@ -25,7 +25,7 @@ import { } from '@backstage/backend-plugin-api'; import { Config, readDurationFromConfig } from '@backstage/config'; import { durationToMilliseconds } from '@backstage/types'; -import { CATALOG_FILTER_EXISTS, CatalogApi } from '@backstage/catalog-client'; +import { CATALOG_FILTER_EXISTS } from '@backstage/catalog-client'; import { getProcessorFiltersFromConfig, Notification, @@ -39,6 +39,7 @@ import { createStreamTransport, } from './transports'; import { UserEntity } from '@backstage/catalog-model'; +import { CatalogService } from '@backstage/plugin-catalog-node'; import { compact } from 'lodash'; import { DefaultAwsCredentialsManager } from '@backstage/integration-aws-node'; import { NotificationTemplateRenderer } from '../extensions'; @@ -62,7 +63,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { constructor( private readonly logger: LoggerService, private readonly config: Config, - private readonly catalog: CatalogApi, + private readonly catalog: CatalogService, private readonly auth: AuthService, private readonly cache?: CacheService, private readonly templateRenderer?: NotificationTemplateRenderer, @@ -152,10 +153,6 @@ export class NotificationsEmailProcessor implements NotificationProcessor { return cached; } - const { token } = await this.auth.getPluginRequestToken({ - onBehalfOf: await this.auth.getOwnServiceCredentials(), - targetPluginId: 'catalog', - }); const entities = await this.catalog.getEntities( { filter: [ @@ -163,7 +160,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { ], fields: ['spec.profile.email'], }, - { token }, + { credentials: await this.auth.getOwnServiceCredentials() }, ); const ret = compact([ ...new Set( @@ -188,11 +185,9 @@ export class NotificationsEmailProcessor implements NotificationProcessor { return cached; } - const { token } = await this.auth.getPluginRequestToken({ - onBehalfOf: await this.auth.getOwnServiceCredentials(), - targetPluginId: 'catalog', + const entity = await this.catalog.getEntityByRef(entityRef, { + credentials: await this.auth.getOwnServiceCredentials(), }); - const entity = await this.catalog.getEntityByRef(entityRef, { token }); const ret: string[] = []; if (entity) { const userEntity = entity as UserEntity; diff --git a/plugins/notifications-backend-module-slack/CHANGELOG.md b/plugins/notifications-backend-module-slack/CHANGELOG.md index e948215902..2effe53d63 100644 --- a/plugins/notifications-backend-module-slack/CHANGELOG.md +++ b/plugins/notifications-backend-module-slack/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-notifications-backend-module-slack +## 0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-notifications-common@0.0.9-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/plugin-notifications-node@0.2.16-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-notifications-node@0.2.16-next.0 + ## 0.1.1 ### Patch Changes diff --git a/plugins/notifications-backend-module-slack/package.json b/plugins/notifications-backend-module-slack/package.json index 513b784baa..bb5ed460df 100644 --- a/plugins/notifications-backend-module-slack/package.json +++ b/plugins/notifications-backend-module-slack/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend-module-slack", - "version": "0.1.1", + "version": "0.1.2-next.1", "description": "The slack backend module for the notifications plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/notifications-backend/CHANGELOG.md b/plugins/notifications-backend/CHANGELOG.md index c2a0369c43..065282a44c 100644 --- a/plugins/notifications-backend/CHANGELOG.md +++ b/plugins/notifications-backend/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-notifications-backend +## 0.5.7-next.1 + +### Patch Changes + +- 1fb5f06: Adds ability for user to turn on/off notifications for specific topics within an origin. +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-notifications-common@0.0.9-next.0 + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/plugin-notifications-node@0.2.16-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12-next.1 + - @backstage/plugin-signals-node@0.1.21-next.1 + +## 0.5.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + - @backstage/plugin-notifications-node@0.2.16-next.0 + - @backstage/plugin-signals-node@0.1.21-next.0 + ## 0.5.6 ### Patch Changes diff --git a/plugins/notifications-backend/migrations/20250317_addTopic.js b/plugins/notifications-backend/migrations/20250317_addTopic.js new file mode 100644 index 0000000000..c8f78a5640 --- /dev/null +++ b/plugins/notifications-backend/migrations/20250317_addTopic.js @@ -0,0 +1,56 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const crypto = require('crypto'); + +exports.up = async function up(knex) { + await knex.schema.alterTable('user_settings', table => { + table.string('topic').nullable().after('origin'); + table.string('settings_key_hash', 64).notNullable(); + table.dropUnique([], 'user_settings_unique_idx'); + }); + + await knex.schema.alterTable('user_settings', table => { + table.unique(['settings_key_hash'], 'user_settings_unique_idx'); + }); + + const rows = await knex('user_settings').select('user', 'channel', 'origin'); + for (const row of rows) { + const rawKey = `${row.user}|${row.channel}|${row.origin}|}`; + const hash = crypto.createHash('sha256').update(rawKey).digest('hex'); + await knex('user_settings') + .where({ + user: row.user, + channel: row.channel, + origin: row.origin, + topic: row.topic, + }) + .update({ settings_key_hash: hash }); + } +}; + +exports.down = async function down(knex) { + await knex.schema.table('user_settings', table => { + table.dropUnique([], 'user_settings_unique_idx'); + table.dropColumn('settings_key_hash'); + table.dropColumn('topic'); + }); + + await knex.schema.alterTable('user_settings', table => { + table.unique(['user', 'channel', 'origin'], { + indexName: 'user_settings_unique_idx', + }); + }); +}; diff --git a/plugins/notifications-backend/package.json b/plugins/notifications-backend/package.json index 7b9387e15f..f94548d377 100644 --- a/plugins/notifications-backend/package.json +++ b/plugins/notifications-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend", - "version": "0.5.6", + "version": "0.5.7-next.1", "backstage": { "role": "backend-plugin", "pluginId": "notifications", @@ -40,7 +40,6 @@ }, "dependencies": { "@backstage/backend-plugin-api": "workspace:^", - "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", diff --git a/plugins/notifications-backend/report.sql.md b/plugins/notifications-backend/report.sql.md index 6b9066e77c..aacb787a61 100644 --- a/plugins/notifications-backend/report.sql.md +++ b/plugins/notifications-backend/report.sql.md @@ -63,14 +63,16 @@ ## Table `user_settings` -| Column | Type | Nullable | Max Length | Default | -| --------- | ------------------- | -------- | ---------- | ------- | -| `channel` | `character varying` | false | 255 | - | -| `enabled` | `boolean` | false | - | `true` | -| `origin` | `character varying` | false | 255 | - | -| `user` | `character varying` | false | 255 | - | +| Column | Type | Nullable | Max Length | Default | +| ------------------- | ------------------- | -------- | ---------- | ------- | +| `channel` | `character varying` | false | 255 | - | +| `enabled` | `boolean` | false | - | `true` | +| `origin` | `character varying` | false | 255 | - | +| `settings_key_hash` | `character varying` | false | 64 | - | +| `topic` | `character varying` | true | 255 | - | +| `user` | `character varying` | false | 255 | - | ### Indices -- `user_settings_unique_idx` (`user`, `channel`, `origin`) unique +- `user_settings_unique_idx` (`settings_key_hash`) unique - `user_settings_user_idx` (`user`) diff --git a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts index 0386b516a2..c7819552cb 100644 --- a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts +++ b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts @@ -157,12 +157,19 @@ const notificationSettings: NotificationSettings = { id: 'Web', origins: [ { - id: 'plugin-test', + id: 'abcd-origin', enabled: true, + topics: [ + { + id: 'efgh-topic', + enabled: false, + }, + ], }, { - id: 'plugin-test2', - enabled: false, + id: 'plugin-test', + enabled: true, + topics: [], }, ], }, diff --git a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts index b258f38e7a..35ecf1d778 100644 --- a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts +++ b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts @@ -30,6 +30,7 @@ import { NotificationSeverity, } from '@backstage/plugin-notifications-common'; import { Knex } from 'knex'; +import crypto from 'crypto'; const migrationsDir = resolvePackagePath( '@backstage/plugin-notifications-backend', @@ -105,6 +106,16 @@ export const normalizeSeverity = (input?: string): NotificationSeverity => { return lower; }; +export const generateSettingsHash = ( + user: string, + channel: string, + origin: string, + topic: string | null, +): string => { + const rawKey = `${user}|${channel}|${origin}|${topic ?? ''}`; + return crypto.createHash('sha256').update(rawKey).digest('hex'); +}; + /** @internal */ export class DatabaseNotificationsStore implements NotificationsStore { private readonly isSQLite = false; @@ -169,10 +180,31 @@ export class DatabaseNotificationsStore implements NotificationsStore { }); chan = acc.channels[acc.channels.length - 1]; } - chan.origins.push({ - id: row.origin, - enabled: Boolean(row.enabled), - }); + let origin = chan.origins.find( + (ori: { id: string }) => ori.id === row.origin, + ); + if (!origin) { + origin = { + id: row.origin, + enabled: true, + topics: [], + }; + chan.origins.push(origin); + } + if (row.topic === null) { + origin.enabled = Boolean(row.enabled); + } else { + let topic = origin.topics.find( + (top: { id: string }) => top.id === row.topic, + ); + if (!topic) { + topic = { + id: row.topic, + enabled: Boolean(row.enabled), + }; + origin.topics.push(topic); + } + } return acc; }, { channels: [] }, @@ -518,10 +550,26 @@ export class DatabaseNotificationsStore implements NotificationsStore { return { origins: rows.map(row => row.origin) }; } + async getUserNotificationTopics(options: { + user: string; + }): Promise<{ topics: { origin: string; topic: string }[] }> { + const rows: { topic: string; origin: string }[] = + await this.db('notification') + .where('user', options.user) + .select('topic', 'origin') + .whereNotNull('topic') + .distinct(); + + return { + topics: rows.map(row => ({ origin: row.origin, topic: row.topic })), + }; + } + async getNotificationSettings(options: { user: string; origin?: string; channel?: string; + topic?: string; }): Promise { const settingsQuery = this.db('user_settings').where( 'user', @@ -534,6 +582,10 @@ export class DatabaseNotificationsStore implements NotificationsStore { if (options.channel) { settingsQuery.where('channel', options.channel); } + + if (options.topic) { + settingsQuery.where('topic', options.topic); + } const settings = await settingsQuery.select(); return this.mapToNotificationSettings(settings); } @@ -543,19 +595,45 @@ export class DatabaseNotificationsStore implements NotificationsStore { settings: NotificationSettings; }): Promise { const rows: { + settings_key_hash: string; user: string; channel: string; origin: string; + topic: string | null; enabled: boolean; }[] = []; - options.settings.channels.map(channel => { - channel.origins.map(origin => { + + options.settings.channels.forEach(channel => { + channel.origins.forEach(origin => { rows.push({ + settings_key_hash: generateSettingsHash( + options.user, + channel.id, + origin.id, + null, + ), user: options.user, channel: channel.id, origin: origin.id, + topic: null, enabled: origin.enabled, }); + + origin.topics?.forEach(topic => { + rows.push({ + settings_key_hash: generateSettingsHash( + options.user, + channel.id, + origin.id, + topic.id, + ), + user: options.user, + channel: channel.id, + origin: origin.id, + topic: topic.id, + enabled: origin.enabled && topic.enabled, + }); + }); }); }); diff --git a/plugins/notifications-backend/src/plugin.ts b/plugins/notifications-backend/src/plugin.ts index 84a200bae1..a5a31d92ed 100644 --- a/plugins/notifications-backend/src/plugin.ts +++ b/plugins/notifications-backend/src/plugin.ts @@ -25,7 +25,7 @@ import { notificationsProcessingExtensionPoint, NotificationsProcessingExtensionPoint, } from '@backstage/plugin-notifications-node'; -import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha'; +import { catalogServiceRef } from '@backstage/plugin-catalog-node'; class NotificationsProcessingExtensionPointImpl implements NotificationsProcessingExtensionPoint diff --git a/plugins/notifications-backend/src/service/getUsersForEntityRef.test.ts b/plugins/notifications-backend/src/service/getUsersForEntityRef.test.ts index eba03511bb..838ded5c18 100644 --- a/plugins/notifications-backend/src/service/getUsersForEntityRef.test.ts +++ b/plugins/notifications-backend/src/service/getUsersForEntityRef.test.ts @@ -28,25 +28,25 @@ describe('getUsersForEntityRef', () => { await expect( getUsersForEntityRef(null, [], { auth: mockServices.auth(), - catalogClient: catalogServiceMock(), + catalog: catalogServiceMock(), }), ).resolves.toEqual([]); }); it('should resolve users without calling catalog', async () => { - const catalogClient = catalogServiceMock(); - jest.spyOn(catalogClient, 'getEntitiesByRefs'); + const catalog = catalogServiceMock(); + jest.spyOn(catalog, 'getEntitiesByRefs'); await expect( getUsersForEntityRef(['user:foo', 'user:ignored'], ['user:ignored'], { auth: mockServices.auth(), - catalogClient, + catalog, }), ).resolves.toEqual(['user:foo']); - expect(catalogClient.getEntitiesByRefs).not.toHaveBeenCalled(); + expect(catalog.getEntitiesByRefs).not.toHaveBeenCalled(); }); it('should resolve group entities to users', async () => { - const catalogClient = catalogServiceMock({ + const catalog = catalogServiceMock({ entities: [ { apiVersion: 'backstage.io/v1alpha1', @@ -91,14 +91,14 @@ describe('getUsersForEntityRef', () => { ['user:default/ignored'], { auth: mockServices.auth(), - catalogClient, + catalog, }, ), ).resolves.toEqual(['user:default/foo', 'user:default/bar']); }); it('should resolve user owner of entity from entity ref', async () => { - const catalogClient = catalogServiceMock({ + const catalog = catalogServiceMock({ entities: [ { apiVersion: 'backstage.io/v1alpha1', @@ -119,13 +119,13 @@ describe('getUsersForEntityRef', () => { await expect( getUsersForEntityRef('component:default/test_component', [], { auth: mockServices.auth(), - catalogClient, + catalog, }), ).resolves.toEqual(['user:default/foo']); }); it('should resolve group owner of entity from entity ref', async () => { - const catalogClient = catalogServiceMock({ + const catalog = catalogServiceMock({ entities: [ { apiVersion: 'backstage.io/v1alpha1', @@ -159,7 +159,7 @@ describe('getUsersForEntityRef', () => { await expect( getUsersForEntityRef('component:default/test_component', [], { auth: mockServices.auth(), - catalogClient, + catalog, }), ).resolves.toEqual(['user:default/foo']); }); diff --git a/plugins/notifications-backend/src/service/getUsersForEntityRef.ts b/plugins/notifications-backend/src/service/getUsersForEntityRef.ts index 9ced24709b..2fa1412992 100644 --- a/plugins/notifications-backend/src/service/getUsersForEntityRef.ts +++ b/plugins/notifications-backend/src/service/getUsersForEntityRef.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { Entity, isGroupEntity, @@ -24,7 +25,7 @@ import { stringifyEntityRef, } from '@backstage/catalog-model'; import { AuthService } from '@backstage/backend-plugin-api'; -import { CatalogApi } from '@backstage/catalog-client'; +import { CatalogService } from '@backstage/plugin-catalog-node'; const isUserEntityRef = (ref: string) => parseEntityRef(ref).kind.toLocaleLowerCase() === 'user'; @@ -45,20 +46,15 @@ export const getUsersForEntityRef = async ( excludeEntityRefs: string | string[], options: { auth: AuthService; - catalogClient: CatalogApi; + catalog: CatalogService; }, ): Promise => { - const { auth, catalogClient } = options; + const { auth, catalog } = options; if (entityRef === null) { return []; } - const { token } = await auth.getPluginRequestToken({ - onBehalfOf: await auth.getOwnServiceCredentials(), - targetPluginId: 'catalog', - }); - const excluded = Array.isArray(excludeEntityRefs) ? excludeEntityRefs : [excludeEntityRefs]; @@ -71,12 +67,12 @@ export const getUsersForEntityRef = async ( const fields = ['kind', 'metadata.name', 'metadata.namespace', 'relations']; let entities: Array = []; if (entityRefs.length > 0) { - const fetchedEntities = await catalogClient.getEntitiesByRefs( + const fetchedEntities = await catalog.getEntitiesByRefs( { entityRefs, fields, }, - { token }, + { credentials: await auth.getOwnServiceCredentials() }, ); entities = fetchedEntities.items; } @@ -114,12 +110,12 @@ export const getUsersForEntityRef = async ( let childGroupUsers: string[][] = []; if (childGroupRefs.length > 0) { - const childGroups = await catalogClient.getEntitiesByRefs( + const childGroups = await catalog.getEntitiesByRefs( { entityRefs: childGroupRefs, fields, }, - { token }, + { credentials: await auth.getOwnServiceCredentials() }, ); childGroupUsers = await Promise.all(childGroups.items.map(mapEntity)); } @@ -145,7 +141,9 @@ export const getUsersForEntityRef = async ( return [ownerRef]; } - const owner = await catalogClient.getEntityByRef(ownerRef, { token }); + const owner = await catalog.getEntityByRef(ownerRef, { + credentials: await auth.getOwnServiceCredentials(), + }); return mapEntity(owner); } diff --git a/plugins/notifications-backend/src/service/router.test.ts b/plugins/notifications-backend/src/service/router.test.ts index 46ee00c554..0538e90697 100644 --- a/plugins/notifications-backend/src/service/router.test.ts +++ b/plugins/notifications-backend/src/service/router.test.ts @@ -307,9 +307,10 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { expect(notifications).toHaveLength(1); }); - it('should not send to user entity if disabled in settings', async () => { + it('should not send to user entity if origin is disabled in settings', async () => { const client = await database.getClient(); await client('user_settings').insert({ + settings_key_hash: 'hash', user: 'user:default/mock', channel: 'Web', origin: 'external:test-service', @@ -335,6 +336,85 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { expect(notifications).toHaveLength(0); }); + it('should not send to user entity if topic is disabled in settings', async () => { + const client = await database.getClient(); + await client('user_settings').insert({ + settings_key_hash: 'hash', + user: 'user:default/mock', + channel: 'Web', + origin: 'external:test-service', + topic: 'test-topic', + enabled: false, + }); + + const response = await sendNotification({ + recipients: { + type: 'entity', + entityRef: ['user:default/mock'], + }, + payload: { + title: 'test notification', + topic: 'test-topic', + }, + }); + + expect(response.status).toEqual(200); + expect(response.body).toEqual([]); + + const notifications = await client('notification') + .where('user', 'user:default/mock') + .select(); + expect(notifications).toHaveLength(0); + }); + + it('should send to user entity if origin is enabled, but topic is disabled in settings', async () => { + const client = await database.getClient(); + await client('user_settings').insert({ + settings_key_hash: 'hash', + user: 'user:default/mock', + channel: 'Web', + origin: 'external:test-service', + enabled: true, + }); + await client('user_settings').insert({ + settings_key_hash: 'hash1', + user: 'user:default/mock', + channel: 'Web', + origin: 'external:test-service', + topic: 'test-topic', + enabled: false, + }); + + const response = await sendNotification({ + recipients: { + type: 'entity', + entityRef: ['user:default/mock'], + }, + payload: { + title: 'test notification', + }, + }); + + expect(response.status).toEqual(200); + expect(response.body).toEqual([ + { + created: expect.any(String), + id: expect.any(String), + origin: 'external:test-service', + payload: { + severity: 'normal', + title: 'test notification', + }, + user: 'user:default/mock', + }, + ]); + + const notifications = await client('notification') + .where('user', 'user:default/mock') + .select(); + expect(notifications).toHaveLength(1); + }); + it('should fail without recipients', async () => { const response = await sendNotification({ payload: { @@ -490,6 +570,7 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { it('should return user settings', async () => { const client = await database.getClient(); await client('user_settings').insert({ + settings_key_hash: 'hash', user: 'user:default/mock', channel: 'Web', origin: 'external:test-service', @@ -502,7 +583,9 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { channels: [ { id: 'Web', - origins: [{ enabled: false, id: 'external:test-service' }], + origins: [ + { enabled: false, id: 'external:test-service', topics: [] }, + ], }, ], }); diff --git a/plugins/notifications-backend/src/service/router.ts b/plugins/notifications-backend/src/service/router.ts index 7f0279b437..f502d49cc4 100644 --- a/plugins/notifications-backend/src/service/router.ts +++ b/plugins/notifications-backend/src/service/router.ts @@ -23,7 +23,7 @@ import { TopicGetOptions, } from '../database'; import { v4 as uuid } from 'uuid'; -import { CatalogApi } from '@backstage/catalog-client'; +import { CatalogService } from '@backstage/plugin-catalog-node'; import { NotificationProcessor, NotificationSendOptions, @@ -38,6 +38,7 @@ import { } from '@backstage/backend-plugin-api'; import { SignalsService } from '@backstage/plugin-signals-node'; import { + ChannelSetting, isNotificationsEnabledFor, NewNotificationSignal, Notification, @@ -45,6 +46,7 @@ import { NotificationSettings, notificationSeverities, NotificationStatus, + OriginSetting, } from '@backstage/plugin-notifications-common'; import { parseEntityOrderFieldParams } from './parseEntityOrderFieldParams'; import { getUsersForEntityRef } from './getUsersForEntityRef'; @@ -61,7 +63,7 @@ export interface RouterOptions { httpAuth: HttpAuthService; userInfo: UserInfoService; signals?: SignalsService; - catalog: CatalogApi; + catalog: CatalogService; processors?: NotificationProcessor[]; } @@ -104,40 +106,86 @@ export async function createRouter( return info.userEntityRef; }; + const getTopicSettings = ( + topic: any, + existingOrigin: OriginSetting | undefined, + defaultEnabled: boolean, + ) => { + const existingTopic = existingOrigin?.topics?.find( + t => t.id === topic.topic, + ); + return { + id: topic.topic, + enabled: existingTopic ? existingTopic.enabled : defaultEnabled, + }; + }; + + const getOriginSettings = ( + originId: string, + existingChannel: ChannelSetting | undefined, + topics: { origin: string; topic: string }[], + ) => { + const existingOrigin = existingChannel?.origins.find( + o => o.id === originId, + ); + const defaultEnabled = existingOrigin ? existingOrigin.enabled : true; + return { + id: originId, + enabled: defaultEnabled, + topics: topics + .filter(t => t.origin === originId) + .map(t => getTopicSettings(t, existingOrigin, defaultEnabled)), + }; + }; + const getNotificationChannels = () => { return [WEB_NOTIFICATION_CHANNEL, ...processors.map(p => p.getName())]; }; + const getChannelSettings = ( + channelId: string, + settings: NotificationSettings, + origins: string[], + topics: { origin: string; topic: string }[], + ) => { + const existingChannel = settings.channels.find(c => c.id === channelId); + if (existingChannel) { + return existingChannel; + } + return { + id: channelId, + origins: origins.map(originId => + getOriginSettings(originId, existingChannel, topics), + ), + }; + }; + const getNotificationSettings = async (user: string) => { const { origins } = await store.getUserNotificationOrigins({ user }); + const { topics } = await store.getUserNotificationTopics({ user }); const settings = await store.getNotificationSettings({ user }); const channels = getNotificationChannels(); - const response: NotificationSettings = { - channels: channels.map(channel => { - const channelSettings = settings.channels.find(c => c.id === channel); - if (channelSettings) { - return channelSettings; - } - return { - id: channel, - origins: origins.map(origin => ({ - id: origin, - enabled: true, - })), - }; - }), + return { + channels: channels.map(channelId => + getChannelSettings(channelId, settings, origins, topics), + ), }; - return response; }; const isNotificationsEnabled = async (opts: { user: string; channel: string; origin: string; + topic: string | null; }) => { const settings = await getNotificationSettings(opts.user); - return isNotificationsEnabledFor(settings, opts.channel, opts.origin); + return isNotificationsEnabledFor( + settings, + opts.channel, + opts.origin, + opts.topic, + ); }; const filterProcessors = async ( @@ -154,6 +202,7 @@ export async function createRouter( user, origin, channel: processor.getName(), + topic: payload.topic ?? null, }); if (!enabled) { continue; @@ -508,6 +557,7 @@ export async function createRouter( user, channel: WEB_NOTIFICATION_CHANNEL, origin: userNotification.origin, + topic: userNotification.payload.topic ?? null, }); let ret = notification; @@ -622,7 +672,7 @@ export async function createRouter( users = await getUsersForEntityRef( entityRef, recipients.excludeEntityRef ?? [], - { auth, catalogClient: catalog }, + { auth, catalog }, ); } catch (e) { throw new InputError('Failed to resolve notification receivers', e); diff --git a/plugins/notifications-common/CHANGELOG.md b/plugins/notifications-common/CHANGELOG.md index 3fd44c9e93..0bcbc7e926 100644 --- a/plugins/notifications-common/CHANGELOG.md +++ b/plugins/notifications-common/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-notifications-common +## 0.0.9-next.0 + +### Patch Changes + +- 1fb5f06: Adds ability for user to turn on/off notifications for specific topics within an origin. +- Updated dependencies + - @backstage/config@1.3.2 + ## 0.0.8 ### Patch Changes diff --git a/plugins/notifications-common/package.json b/plugins/notifications-common/package.json index d4df76a817..cdca935d38 100644 --- a/plugins/notifications-common/package.json +++ b/plugins/notifications-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-common", - "version": "0.0.8", + "version": "0.0.9-next.0", "description": "Common functionalities for the notifications plugin", "backstage": { "role": "common-library", diff --git a/plugins/notifications-common/report.api.md b/plugins/notifications-common/report.api.md index 167b37349d..93889b206b 100644 --- a/plugins/notifications-common/report.api.md +++ b/plugins/notifications-common/report.api.md @@ -5,6 +5,12 @@ ```ts import { Config } from '@backstage/config'; +// @public (undocumented) +export type ChannelSetting = { + id: string; + origins: OriginSetting[]; +}; + // @public (undocumented) export const getProcessorFiltersFromConfig: ( config: Config, @@ -15,6 +21,7 @@ export const isNotificationsEnabledFor: ( settings: NotificationSettings, channelId: string, originId: string, + topicId: string | null, ) => boolean; // @public (undocumented) @@ -62,13 +69,7 @@ export type NotificationReadSignal = { // @public (undocumented) export type NotificationSettings = { - channels: { - id: string; - origins: { - id: string; - enabled: boolean; - }[]; - }[]; + channels: ChannelSetting[]; }; // @public @@ -85,4 +86,17 @@ export type NotificationStatus = { unread: number; read: number; }; + +// @public (undocumented) +export type OriginSetting = { + id: string; + enabled: boolean; + topics?: TopicSetting[]; +}; + +// @public (undocumented) +export type TopicSetting = { + id: string; + enabled: boolean; +}; ``` diff --git a/plugins/notifications-common/src/types.ts b/plugins/notifications-common/src/types.ts index 237f93f40a..48f89cbbb4 100644 --- a/plugins/notifications-common/src/types.ts +++ b/plugins/notifications-common/src/types.ts @@ -129,12 +129,31 @@ export type NotificationProcessorFilters = { /** * @public */ -export type NotificationSettings = { - channels: { - id: string; - origins: { - id: string; - enabled: boolean; - }[]; - }[]; +export type TopicSetting = { + id: string; + enabled: boolean; +}; + +/** + * @public + */ +export type OriginSetting = { + id: string; + enabled: boolean; + topics?: TopicSetting[]; +}; + +/** + * @public + */ +export type ChannelSetting = { + id: string; + origins: OriginSetting[]; +}; + +/** + * @public + */ +export type NotificationSettings = { + channels: ChannelSetting[]; }; diff --git a/plugins/notifications-common/src/utils.ts b/plugins/notifications-common/src/utils.ts index c57412bd97..b95df5bea5 100644 --- a/plugins/notifications-common/src/utils.ts +++ b/plugins/notifications-common/src/utils.ts @@ -20,6 +20,7 @@ export const isNotificationsEnabledFor = ( settings: NotificationSettings, channelId: string, originId: string, + topicId: string | null, ) => { const channel = settings.channels.find(c => c.id === channelId); if (!channel) { @@ -30,5 +31,12 @@ export const isNotificationsEnabledFor = ( if (!origin) { return true; } - return origin.enabled; + if (topicId === null) { + return origin.enabled; + } + const topic = origin.topics?.find(t => t.id === topicId); + if (!topic) { + return origin.enabled; + } + return topic.enabled; }; diff --git a/plugins/notifications-node/CHANGELOG.md b/plugins/notifications-node/CHANGELOG.md index be8882e384..abfa9e282b 100644 --- a/plugins/notifications-node/CHANGELOG.md +++ b/plugins/notifications-node/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-notifications-node +## 0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-notifications-common@0.0.9-next.0 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/plugin-signals-node@0.1.21-next.1 + +## 0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-signals-node@0.1.21-next.0 + ## 0.2.15 ### Patch Changes diff --git a/plugins/notifications-node/package.json b/plugins/notifications-node/package.json index 9e3e38aca8..ce823ffe0d 100644 --- a/plugins/notifications-node/package.json +++ b/plugins/notifications-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-node", - "version": "0.2.15", + "version": "0.2.16-next.1", "description": "Node.js library for the notifications plugin", "backstage": { "role": "node-library", diff --git a/plugins/notifications/CHANGELOG.md b/plugins/notifications/CHANGELOG.md index 55632ba3c3..a9df09639b 100644 --- a/plugins/notifications/CHANGELOG.md +++ b/plugins/notifications/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-notifications +## 0.5.6-next.1 + +### Patch Changes + +- 1fb5f06: Adds ability for user to turn on/off notifications for specific topics within an origin. +- Updated dependencies + - @backstage/plugin-notifications-common@0.0.9-next.0 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/theme@0.6.6 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-react@0.0.13 + +## 0.5.6-next.0 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + ## 0.5.5 ### Patch Changes diff --git a/plugins/notifications/package.json b/plugins/notifications/package.json index 3375aa756f..4a9f2fc583 100644 --- a/plugins/notifications/package.json +++ b/plugins/notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications", - "version": "0.5.5", + "version": "0.5.6-next.1", "backstage": { "role": "frontend-plugin", "pluginId": "notifications", diff --git a/plugins/notifications/report.api.md b/plugins/notifications/report.api.md index cdf6d9d319..62c41d07c7 100644 --- a/plugins/notifications/report.api.md +++ b/plugins/notifications/report.api.md @@ -207,6 +207,7 @@ export function useNotificationsApi( // @public (undocumented) export const UserNotificationSettingsCard: (props: { originNames?: Record; + topicNames?: Record; }) => JSX_2.Element; // (No @packageDocumentation comment for this package) diff --git a/plugins/notifications/src/alpha.tsx b/plugins/notifications/src/alpha.tsx index d7c4ec27b6..4424659593 100644 --- a/plugins/notifications/src/alpha.tsx +++ b/plugins/notifications/src/alpha.tsx @@ -54,6 +54,7 @@ const api = ApiBlueprint.make({ /** @alpha */ export default createFrontendPlugin({ pluginId: 'notifications', + info: { packageJson: () => import('../package.json') }, routes: convertLegacyRouteRefs({ root: rootRouteRef, }), diff --git a/plugins/notifications/src/components/UserNotificationSettingsCard/NoBorderTableCell.tsx b/plugins/notifications/src/components/UserNotificationSettingsCard/NoBorderTableCell.tsx new file mode 100644 index 0000000000..71cd52f8a8 --- /dev/null +++ b/plugins/notifications/src/components/UserNotificationSettingsCard/NoBorderTableCell.tsx @@ -0,0 +1,24 @@ +/* + * 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 { withStyles } from '@material-ui/core/styles'; +import MuiTableCell from '@material-ui/core/TableCell'; + +export const NoBorderTableCell = withStyles({ + root: { + borderBottom: 'none', + }, +})(MuiTableCell); diff --git a/plugins/notifications/src/components/UserNotificationSettingsCard/OriginRow.tsx b/plugins/notifications/src/components/UserNotificationSettingsCard/OriginRow.tsx new file mode 100644 index 0000000000..d8d347c202 --- /dev/null +++ b/plugins/notifications/src/components/UserNotificationSettingsCard/OriginRow.tsx @@ -0,0 +1,91 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { + ChannelSetting, + isNotificationsEnabledFor, + NotificationSettings, + OriginSetting, +} from '@backstage/plugin-notifications-common'; +import IconButton from '@material-ui/core/IconButton'; +import Switch from '@material-ui/core/Switch'; +import TableRow from '@material-ui/core/TableRow'; +import Tooltip from '@material-ui/core/Tooltip'; +import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown'; +import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp'; +import { NoBorderTableCell } from './NoBorderTableCell'; +import { useNotificationFormat } from './UserNotificationSettingsCard'; + +export const OriginRow = (props: { + channel: ChannelSetting; + origin: OriginSetting; + settings: NotificationSettings; + handleChange: ( + channel: string, + origin: string, + topic: string | null, + enabled: boolean, + ) => void; + open: boolean; + handleRowToggle: (originId: string) => void; +}) => { + const { channel, origin, settings, handleChange, open, handleRowToggle } = + props; + const { formatOriginName } = useNotificationFormat(); + return ( + + + {origin.topics && origin.topics.length > 0 && ( + + handleRowToggle(origin.id)} + > + {open ? : } + + + )} + + {formatOriginName(origin.id)} + all + {settings.channels.map(ch => ( + + + ) => { + handleChange(ch.id, origin.id, null, event.target.checked); + }} + /> + + + ))} + + ); +}; diff --git a/plugins/notifications/src/components/UserNotificationSettingsCard/TopicRow.tsx b/plugins/notifications/src/components/UserNotificationSettingsCard/TopicRow.tsx new file mode 100644 index 0000000000..5c9c3cbacc --- /dev/null +++ b/plugins/notifications/src/components/UserNotificationSettingsCard/TopicRow.tsx @@ -0,0 +1,80 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { + isNotificationsEnabledFor, + NotificationSettings, + OriginSetting, + TopicSetting, +} from '@backstage/plugin-notifications-common'; +import TableRow from '@material-ui/core/TableRow'; +import Tooltip from '@material-ui/core/Tooltip'; +import Switch from '@material-ui/core/Switch'; +import { withStyles } from '@material-ui/core/styles'; +import { NoBorderTableCell } from './NoBorderTableCell'; +import { useNotificationFormat } from './UserNotificationSettingsCard'; + +const TopicTableRow = withStyles({ + root: { + paddingLeft: '4px', + }, +})(TableRow); + +export const TopicRow = (props: { + topic: TopicSetting; + origin: OriginSetting; + settings: NotificationSettings; + handleChange: ( + channel: string, + origin: string, + topic: string | null, + enabled: boolean, + ) => void; +}) => { + const { topic, origin, settings, handleChange } = props; + const { formatOriginName, formatTopicName } = useNotificationFormat(); + return ( + + + + {formatTopicName(topic.id)} + {settings.channels.map(ch => ( + + + ) => { + handleChange(ch.id, origin.id, topic.id, event.target.checked); + }} + /> + + + ))} + + ); +}; diff --git a/plugins/notifications/src/components/UserNotificationSettingsCard/UserNotificationSettingsCard.tsx b/plugins/notifications/src/components/UserNotificationSettingsCard/UserNotificationSettingsCard.tsx index 1d4308bb7e..fbf7fafc28 100644 --- a/plugins/notifications/src/components/UserNotificationSettingsCard/UserNotificationSettingsCard.tsx +++ b/plugins/notifications/src/components/UserNotificationSettingsCard/UserNotificationSettingsCard.tsx @@ -14,17 +14,74 @@ * limitations under the License. */ -import { useState, useEffect } from 'react'; +import { createContext, useState, useContext, useEffect } from 'react'; import { ErrorPanel, InfoCard, Progress } from '@backstage/core-components'; import { useNotificationsApi } from '../../hooks'; import { NotificationSettings } from '@backstage/plugin-notifications-common'; import { notificationsApiRef } from '../../api'; import { useApi } from '@backstage/core-plugin-api'; import { UserNotificationSettingsPanel } from './UserNotificationSettingsPanel'; +import { capitalize } from 'lodash'; + +type FormatContextType = { + formatOriginName: (id: string) => string; + formatTopicName: (id: string) => string; +}; + +const NotificationFormatContext = createContext( + undefined, +); + +export const useNotificationFormat = () => { + const context = useContext(NotificationFormatContext); + if (!context) + throw new Error( + 'useNotificationFormat must be used within a NotificationFormatProvider', + ); + return context; +}; + +type Props = { + children: React.ReactNode; + originMap: Record | undefined; + topicMap: Record | undefined; +}; + +export const NotificationFormatProvider = ({ + children, + originMap, + topicMap, +}: Props) => { + const formatName = ( + id: string, + nameMap: Record | undefined, + ) => { + if (nameMap && id in nameMap) { + return nameMap[id]; + } + return capitalize(id.replaceAll(/[-_:]/g, ' ')); + }; + + const formatOriginName = (originId: string) => { + return formatName(originId, originMap); + }; + + const formatTopicName = (topicId: string) => { + return formatName(topicId, topicMap); + }; + return ( + + {children} + + ); +}; /** @public */ export const UserNotificationSettingsCard = (props: { originNames?: Record; + topicNames?: Record; }) => { const [settings, setNotificationSettings] = useState< NotificationSettings | undefined @@ -52,11 +109,15 @@ export const UserNotificationSettingsCard = (props: { {loading && } {error && } {settings && ( - + + + )} ); diff --git a/plugins/notifications/src/components/UserNotificationSettingsCard/UserNotificationSettingsPanel.tsx b/plugins/notifications/src/components/UserNotificationSettingsCard/UserNotificationSettingsPanel.tsx index 8841b2829a..aa079100be 100644 --- a/plugins/notifications/src/components/UserNotificationSettingsCard/UserNotificationSettingsPanel.tsx +++ b/plugins/notifications/src/components/UserNotificationSettingsCard/UserNotificationSettingsPanel.tsx @@ -14,11 +14,8 @@ * limitations under the License. */ -import { ChangeEvent } from 'react'; -import { - isNotificationsEnabledFor, - NotificationSettings, -} from '@backstage/plugin-notifications-common'; +import { useState } from 'react'; +import { NotificationSettings } from '@backstage/plugin-notifications-common'; import Table from '@material-ui/core/Table'; import MuiTableCell from '@material-ui/core/TableCell'; import { withStyles } from '@material-ui/core/styles'; @@ -26,9 +23,8 @@ import TableHead from '@material-ui/core/TableHead'; import Typography from '@material-ui/core/Typography'; import TableBody from '@material-ui/core/TableBody'; import TableRow from '@material-ui/core/TableRow'; -import Switch from '@material-ui/core/Switch'; -import { capitalize } from 'lodash'; -import Tooltip from '@material-ui/core/Tooltip'; +import { TopicRow } from './TopicRow'; +import { OriginRow } from './OriginRow'; const TableCell = withStyles({ root: { @@ -40,19 +36,26 @@ export const UserNotificationSettingsPanel = (props: { settings: NotificationSettings; onChange: (settings: NotificationSettings) => void; originNames?: Record; + topicNames?: Record; }) => { const { settings, onChange } = props; - const allOrigins = [ - ...new Set( - settings.channels.flatMap(channel => - channel.origins.map(origin => origin.id), - ), - ), - ]; + const [expandedRows, setExpandedRows] = useState>(new Set()); + const handleRowToggle = (originId: string) => { + setExpandedRows(prevState => { + const newExpandedRows = new Set(prevState); + if (newExpandedRows.has(originId)) { + newExpandedRows.delete(originId); + } else { + newExpandedRows.add(originId); + } + return newExpandedRows; + }); + }; const handleChange = ( channelId: string, originId: string, + topicId: string | null, enabled: boolean, ) => { const updatedSettings = { @@ -66,9 +69,30 @@ export const UserNotificationSettingsPanel = (props: { if (origin.id !== originId) { return origin; } + + if (topicId === null) { + return { + ...origin, + enabled, + topics: + origin.topics?.map(topic => { + return { ...topic, enabled }; + }) ?? [], + }; + } + return { ...origin, - enabled, + topics: + origin.topics?.map(topic => { + if (topic.id === topicId) { + return { + ...topic, + enabled: origin.enabled ? enabled : origin.enabled, + }; + } + return topic; + }) ?? [], }; }), }; @@ -77,14 +101,7 @@ export const UserNotificationSettingsPanel = (props: { onChange(updatedSettings); }; - const formatOriginName = (originId: string) => { - if (props.originNames && originId in props.originNames) { - return props.originNames[originId]; - } - return capitalize(originId.replaceAll(/[_:]/g, ' ')); - }; - - if (settings.channels.length === 0 || allOrigins.length === 0) { + if (settings.channels.length === 0) { return ( No notification settings available, check back later @@ -96,44 +113,47 @@ export const UserNotificationSettingsPanel = (props: { + Origin + + Topic + {settings.channels.map(channel => ( - - {channel.id} + + + {channel.id} + ))} - {allOrigins.map(origin => ( - - {formatOriginName(origin)} - {settings.channels.map(channel => ( - - - ) => { - handleChange(channel.id, origin, event.target.checked); - }} + {settings.channels.map(channel => + channel.origins.flatMap(origin => [ + , + ...(expandedRows.has(origin.id) + ? origin.topics?.map(topic => ( + - - - ))} - - ))} + )) || [] + : []), + ]), + )}
); diff --git a/plugins/org-react/CHANGELOG.md b/plugins/org-react/CHANGELOG.md index 40add6dcbc..93db912391 100644 --- a/plugins/org-react/CHANGELOG.md +++ b/plugins/org-react/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-org-react +## 0.1.39-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + +## 0.1.39-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.18.1-next.0 + ## 0.1.38 ### Patch Changes diff --git a/plugins/org-react/package.json b/plugins/org-react/package.json index 3c50b863c4..78e690e390 100644 --- a/plugins/org-react/package.json +++ b/plugins/org-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org-react", - "version": "0.1.38", + "version": "0.1.39-next.1", "backstage": { "role": "web-library", "pluginId": "org", diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index 1f15d5ddf9..ff423ab5f6 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-org +## 0.6.40-next.1 + +### Patch Changes + +- 4fe364f: Org plugin support i18n +- Updated dependencies + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/plugin-catalog-common@1.1.4 + +## 0.6.40-next.0 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + ## 0.6.39 ### Patch Changes diff --git a/plugins/org/package.json b/plugins/org/package.json index 05952470f4..ea9d705feb 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org", - "version": "0.6.39", + "version": "0.6.40-next.1", "description": "A Backstage plugin that helps you create entity pages for your organization", "backstage": { "role": "frontend-plugin", diff --git a/plugins/org/report-alpha.api.md b/plugins/org/report-alpha.api.md index 55397c259c..840a5472dd 100644 --- a/plugins/org/report-alpha.api.md +++ b/plugins/org/report-alpha.api.md @@ -13,6 +13,7 @@ import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) const _default: FrontendPlugin< @@ -203,5 +204,38 @@ const _default: FrontendPlugin< >; export default _default; +// @alpha (undocumented) +export const orgTranslationRef: TranslationRef< + 'org', + { + readonly 'groupProfileCard.groupNotFound': 'Group not found'; + readonly 'groupProfileCard.editIconButtonTitle': 'Edit Metadata'; + readonly 'groupProfileCard.refreshIconButtonTitle': 'Schedule entity refresh'; + readonly 'groupProfileCard.refreshIconButtonAriaLabel': 'Refresh'; + readonly 'groupProfileCard.listItemTitle.email': 'Email'; + readonly 'groupProfileCard.listItemTitle.entityRef': 'Entity Ref'; + readonly 'groupProfileCard.listItemTitle.parentGroup': 'Parent Group'; + readonly 'groupProfileCard.listItemTitle.childGroups': 'Child Groups'; + readonly 'membersListCard.title': 'Members'; + readonly 'membersListCard.subtitle': 'of {{groupName}}'; + readonly 'membersListCard.paginationLabel': ', page {{page}} of {{nbPages}}'; + readonly 'membersListCard.noMembersDescription': 'This group has no members.'; + readonly 'membersListCard.aggregateMembersToggle.ariaLabel': 'Users Type Switch'; + readonly 'membersListCard.aggregateMembersToggle.directMembers': 'Direct Members'; + readonly 'membersListCard.aggregateMembersToggle.aggregatedMembers': 'Aggregated Members'; + readonly 'ownershipCard.title': 'Ownership'; + readonly 'ownershipCard.aggregateRelationsToggle.ariaLabel': 'Ownership Type Switch'; + readonly 'ownershipCard.aggregateRelationsToggle.directRelations': 'Direct Relations'; + readonly 'ownershipCard.aggregateRelationsToggle.aggregatedRelations': 'Aggregated Relations'; + readonly 'userProfileCard.editIconButtonTitle': 'Edit Metadata'; + readonly 'userProfileCard.listItemTitle.email': 'Email'; + readonly 'userProfileCard.listItemTitle.memberOf': 'Member of'; + readonly 'userProfileCard.userNotFound': 'User not found'; + readonly 'userProfileCard.moreGroupButtonTitle': '...More ({{number}})'; + readonly 'userProfileCard.allGroupDialog.title': "All {{name}}'s groups:"; + readonly 'userProfileCard.allGroupDialog.closeButtonTitle': 'Close'; + } +>; + // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/org/src/alpha.tsx b/plugins/org/src/alpha.tsx index 46f07a8cf0..e5d6aa6675 100644 --- a/plugins/org/src/alpha.tsx +++ b/plugins/org/src/alpha.tsx @@ -87,6 +87,7 @@ const EntityUserProfileCard = EntityCardBlueprint.makeWithOverrides({ /** @alpha */ export default createFrontendPlugin({ pluginId: 'org', + info: { packageJson: () => import('../package.json') }, extensions: [ EntityGroupProfileCard, EntityMembersListCard, @@ -97,3 +98,5 @@ export default createFrontendPlugin({ catalogIndex: catalogIndexRouteRef, }), }); + +export { orgTranslationRef } from './translation'; diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx index 35eb82334c..5c8757a4dd 100644 --- a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx +++ b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx @@ -55,6 +55,8 @@ import PermIdentityIcon from '@material-ui/icons/PermIdentity'; import { LinksGroup } from '../../Meta'; import { useEntityPermission } from '@backstage/plugin-catalog-react/alpha'; import { catalogEntityRefreshPermission } from '@backstage/plugin-catalog-common/alpha'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { orgTranslationRef } from '../../../../translation'; const CardTitle = (props: { title: string }) => ( @@ -74,6 +76,7 @@ export const GroupProfileCard = (props: { const { allowed: canRefresh } = useEntityPermission( catalogEntityRefreshPermission, ); + const { t } = useTranslationRef(orgTranslationRef); const refreshEntity = useCallback(async () => { await catalogApi.refreshEntity(stringifyEntityRef(group)); @@ -85,7 +88,9 @@ export const GroupProfileCard = (props: { }, [catalogApi, alertApi, group]); if (!group) { - return Group not found; + return ( + {t('groupProfileCard.groupNotFound')} + ); } const { @@ -111,15 +116,19 @@ export const GroupProfileCard = (props: { const emailHref = profile?.email ? `mailto:${profile.email}` : '#'; const infoCardAction = entityMetadataEditUrl ? ( ) : ( - + ); @@ -133,8 +142,8 @@ export const GroupProfileCard = (props: { <> {allowRefresh && canRefresh && ( @@ -152,31 +161,33 @@ export const GroupProfileCard = (props: { - + {profile?.email && ( - + {profile.email}} - secondary="Email" + secondary={t('groupProfileCard.listItemTitle.email')} /> )} - + @@ -191,12 +202,14 @@ export const GroupProfileCard = (props: { 'N/A' ) } - secondary="Parent Group" + secondary={t('groupProfileCard.listItemTitle.parentGroup')} /> - + @@ -211,7 +224,7 @@ export const GroupProfileCard = (props: { 'N/A' ) } - secondary="Child Groups" + secondary={t('groupProfileCard.listItemTitle.childGroups')} /> {props?.showLinks && } diff --git a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx index c051de5ddb..07119ba85b 100644 --- a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx +++ b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx @@ -48,6 +48,8 @@ import { removeDuplicateEntitiesFrom, } from '../../../../helpers/helpers'; import { EntityRelationAggregation } from '../../types'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { orgTranslationRef } from '../../../../translation'; /** @public */ export type MemberComponentClassKey = 'card' | 'avatar'; @@ -157,8 +159,9 @@ export const MembersListCard = (props: { relationsType?: EntityRelationAggregation; relationAggregation?: EntityRelationAggregation; }) => { + const { t } = useTranslationRef(orgTranslationRef); const { - memberDisplayTitle = 'Members', + memberDisplayTitle = t('membersListCard.title'), pageSize = 50, showAggregateMembersToggle, relationType = 'memberof', @@ -236,7 +239,13 @@ export const MembersListCard = (props: { } const nbPages = Math.ceil((members?.length || 0) / pageSize); - const paginationLabel = nbPages < 2 ? '' : `, page ${page} of ${nbPages}`; + const paginationLabel = + nbPages < 2 + ? '' + : t('membersListCard.paginationLabel', { + page: String(page), + nbPages: String(nbPages), + }); const pagination = ( - - This group has no {memberDisplayTitle.toLocaleLowerCase()}. - + {t('membersListCard.noMembersDescription')} ); } @@ -273,23 +280,29 @@ export const MembersListCard = (props: { title={`${memberDisplayTitle} (${ members?.length || 0 }${paginationLabel})`} - subheader={`of ${displayName}`} + subheader={t('membersListCard.subtitle', { + groupName: displayName, + })} {...(nbPages <= 1 ? {} : { actions: pagination })} className={classes.root} cardClassName={classes.cardContent} > {showAggregateMembersToggle && ( <> - Direct Members + {t('membersListCard.aggregateMembersToggle.directMembers')} { setShowAggregateMembers(!showAggregateMembers); }} - inputProps={{ 'aria-label': 'Users Type Switch' }} + inputProps={{ + 'aria-label': t( + 'membersListCard.aggregateMembersToggle.ariaLabel', + ), + }} /> - Aggregated Members + {t('membersListCard.aggregateMembersToggle.aggregatedMembers')} )} {showAggregateMembers && loadingDescendantMembers ? ( diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx index d179573b09..ff9fd6cec8 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx @@ -26,6 +26,8 @@ import { makeStyles } from '@material-ui/core/styles'; import { useEffect, useState } from 'react'; import { ComponentsGrid } from './ComponentsGrid'; import { EntityRelationAggregation } from '../types'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { orgTranslationRef } from '../../../translation'; const useStyles = makeStyles(theme => ({ card: { @@ -83,6 +85,7 @@ export const OwnershipCard = (props: { hideRelationsToggle === undefined ? false : hideRelationsToggle; const classes = useStyles(); const { entity } = useEntity(); + const { t } = useTranslationRef(orgTranslationRef); const defaultRelationAggregation = entity.kind === 'User' ? 'aggregated' : 'direct'; @@ -98,7 +101,7 @@ export const OwnershipCard = (props: { return ( - Direct Relations + {t('ownershipCard.aggregateRelationsToggle.directRelations')} - Aggregated Relations + {t('ownershipCard.aggregateRelationsToggle.aggregatedRelations')} diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx index 50cb0e1fef..fdfe3fd5c3 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx @@ -55,6 +55,8 @@ import { LinksGroup } from '../../Meta'; import PersonIcon from '@material-ui/icons/Person'; import { useCallback, useState } from 'react'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { orgTranslationRef } from '../../../../translation'; const useStyles = makeStyles((theme: any) => ({ closeButton: { @@ -92,6 +94,7 @@ export const UserProfileCard = (props: { const classes = useStyles(); const { entity: user } = useEntity(); const [isAllGroupsDialogOpen, setIsAllGroupsDialogOpen] = useState(false); + const { t } = useTranslationRef(orgTranslationRef); const toggleAllGroupsDialog = useCallback( () => @@ -102,7 +105,7 @@ export const UserProfileCard = (props: { ); if (!user) { - return User not found; + return {t('userProfileCard.userNotFound')}; } const entityMetadataEditUrl = @@ -127,8 +130,8 @@ export const UserProfileCard = (props: { <> {entityMetadataEditUrl && ( @@ -148,7 +151,7 @@ export const UserProfileCard = (props: { {profile?.email && ( - + @@ -161,7 +164,7 @@ export const UserProfileCard = (props: { {maxRelations === undefined || maxRelations > 0 ? ( - + @@ -179,9 +182,11 @@ export const UserProfileCard = (props: { onClick={toggleAllGroupsDialog} disableRipple > - {` ...More (${ - memberOfRelations.length - maxRelations - })`} + {t('userProfileCard.moreGroupButtonTitle', { + number: String( + memberOfRelations.length - maxRelations, + ), + })} ) : null} @@ -203,10 +208,12 @@ export const UserProfileCard = (props: { fullWidth > - All {user.metadata.name}'s groups: + {t('userProfileCard.allGroupDialog.title', { + name: user.metadata.name, + })} @@ -216,7 +223,9 @@ export const UserProfileCard = (props: { - + diff --git a/plugins/org/src/translation.ts b/plugins/org/src/translation.ts new file mode 100644 index 0000000000..b7dfda6937 --- /dev/null +++ b/plugins/org/src/translation.ts @@ -0,0 +1,69 @@ +/* + * 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 { createTranslationRef } from '@backstage/frontend-plugin-api'; + +/** + * @alpha + */ +export const orgTranslationRef = createTranslationRef({ + id: 'org', + messages: { + groupProfileCard: { + groupNotFound: 'Group not found', + editIconButtonTitle: 'Edit Metadata', + refreshIconButtonTitle: 'Schedule entity refresh', + refreshIconButtonAriaLabel: 'Refresh', + listItemTitle: { + entityRef: 'Entity Ref', + email: 'Email', + parentGroup: 'Parent Group', + childGroups: 'Child Groups', + }, + }, + membersListCard: { + title: 'Members', + subtitle: 'of {{groupName}}', + paginationLabel: ', page {{page}} of {{nbPages}}', + noMembersDescription: 'This group has no members.', + aggregateMembersToggle: { + directMembers: 'Direct Members', + aggregatedMembers: 'Aggregated Members', + ariaLabel: 'Users Type Switch', + }, + }, + ownershipCard: { + title: 'Ownership', + aggregateRelationsToggle: { + directRelations: 'Direct Relations', + aggregatedRelations: 'Aggregated Relations', + ariaLabel: 'Ownership Type Switch', + }, + }, + userProfileCard: { + userNotFound: 'User not found', + editIconButtonTitle: 'Edit Metadata', + listItemTitle: { + email: 'Email', + memberOf: 'Member of', + }, + moreGroupButtonTitle: '...More ({{number}})', + allGroupDialog: { + title: "All {{name}}'s groups:", + closeButtonTitle: 'Close', + }, + }, + }, +}); diff --git a/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md b/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md index 21be629da2..0d5f459c4c 100644 --- a/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md +++ b/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-permission-backend-module-allow-all-policy +## 0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1-next.1 + +## 0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-permission-node@0.10.1-next.0 + ## 0.2.8 ### Patch Changes diff --git a/plugins/permission-backend-module-policy-allow-all/package.json b/plugins/permission-backend-module-policy-allow-all/package.json index a1c8e3a00d..3ef2bcd7e6 100644 --- a/plugins/permission-backend-module-policy-allow-all/package.json +++ b/plugins/permission-backend-module-policy-allow-all/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend-module-allow-all-policy", - "version": "0.2.8", + "version": "0.2.9-next.1", "description": "Allow all policy backend module for the permission plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/permission-backend/CHANGELOG.md b/plugins/permission-backend/CHANGELOG.md index 688d196dbb..8d0849da56 100644 --- a/plugins/permission-backend/CHANGELOG.md +++ b/plugins/permission-backend/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-permission-backend +## 0.7.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1-next.1 + +## 0.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-permission-node@0.10.1-next.0 + ## 0.7.0 ### Minor Changes diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index 3df015a524..ef970b4c5c 100644 --- a/plugins/permission-backend/package.json +++ b/plugins/permission-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend", - "version": "0.7.0", + "version": "0.7.1-next.1", "backstage": { "role": "backend-plugin", "pluginId": "permission", diff --git a/plugins/permission-node/CHANGELOG.md b/plugins/permission-node/CHANGELOG.md index c0d3f72b5d..c86d51f9c2 100644 --- a/plugins/permission-node/CHANGELOG.md +++ b/plugins/permission-node/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-permission-node +## 0.10.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/plugin-permission-common@0.9.0 + +## 0.10.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + ## 0.10.0 ### Minor Changes diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json index e268173504..805b1c919b 100644 --- a/plugins/permission-node/package.json +++ b/plugins/permission-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-node", - "version": "0.10.0", + "version": "0.10.1-next.1", "description": "Common permission and authorization utilities for backend plugins", "backstage": { "role": "node-library", diff --git a/plugins/proxy-backend/CHANGELOG.md b/plugins/proxy-backend/CHANGELOG.md index a394904443..924fc7c193 100644 --- a/plugins/proxy-backend/CHANGELOG.md +++ b/plugins/proxy-backend/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-proxy-backend +## 0.6.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/types@1.2.1 + - @backstage/plugin-proxy-node@0.1.5-next.1 + +## 0.6.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-proxy-node@0.1.5-next.0 + ## 0.6.2 ### Patch Changes diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index ee777588cb..fc3c2d3147 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-proxy-backend", - "version": "0.6.2", + "version": "0.6.3-next.1", "description": "A Backstage backend plugin that helps you set up proxy endpoints in the backend", "backstage": { "role": "backend-plugin", diff --git a/plugins/proxy-node/CHANGELOG.md b/plugins/proxy-node/CHANGELOG.md index 087a4d3d6b..c1cfc3a736 100644 --- a/plugins/proxy-node/CHANGELOG.md +++ b/plugins/proxy-node/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-proxy-node +## 0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + ## 0.1.4 ### Patch Changes diff --git a/plugins/proxy-node/package.json b/plugins/proxy-node/package.json index 1bae61507b..a99b2b6913 100644 --- a/plugins/proxy-node/package.json +++ b/plugins/proxy-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-proxy-node", - "version": "0.1.4", + "version": "0.1.5-next.1", "description": "The plugin-proxy-node module for @backstage/plugin-proxy-backend", "backstage": { "role": "node-library", diff --git a/plugins/scaffolder-backend-module-azure/CHANGELOG.md b/plugins/scaffolder-backend-module-azure/CHANGELOG.md index aa2614114a..a5e9c298e9 100644 --- a/plugins/scaffolder-backend-module-azure/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-azure/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-scaffolder-backend-module-azure +## 0.2.10-next.1 + +### Patch Changes + +- e89d7b6: Migrating `azure` actions to using the new `zod` schema format +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + +## 0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + ## 0.2.9 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-azure/package.json b/plugins/scaffolder-backend-module-azure/package.json index 17e0781031..975a446efc 100644 --- a/plugins/scaffolder-backend-module-azure/package.json +++ b/plugins/scaffolder-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-azure", - "version": "0.2.9", + "version": "0.2.10-next.1", "description": "The azure module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-azure/report.api.md b/plugins/scaffolder-backend-module-azure/report.api.md index 159bf02a00..dcd33d36e8 100644 --- a/plugins/scaffolder-backend-module-azure/report.api.md +++ b/plugins/scaffolder-backend-module-azure/report.api.md @@ -5,7 +5,6 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; -import { JsonObject } from '@backstage/types'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; @@ -20,16 +19,21 @@ export function createPublishAzureAction(options: { }): TemplateAction< { repoUrl: string; - description?: string; - defaultBranch?: string; - sourcePath?: string; - token?: string; - gitCommitMessage?: string; - gitAuthorName?: string; - gitAuthorEmail?: string; - signCommit?: boolean; + description?: string | undefined; + defaultBranch?: string | undefined; + sourcePath?: string | undefined; + token?: string | undefined; + gitCommitMessage?: string | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; + signCommit?: boolean | undefined; }, - JsonObject, - 'v1' + { + remoteUrl?: string | undefined; + repoContentsUrl?: string | undefined; + repositoryId?: string | undefined; + commitHash?: string | undefined; + }, + 'v2' >; ``` diff --git a/plugins/scaffolder-backend-module-azure/src/actions/azure.ts b/plugins/scaffolder-backend-module-azure/src/actions/azure.ts index 8c1f5afec8..0b806fdb2d 100644 --- a/plugins/scaffolder-backend-module-azure/src/actions/azure.ts +++ b/plugins/scaffolder-backend-module-azure/src/actions/azure.ts @@ -45,92 +45,92 @@ export function createPublishAzureAction(options: { }) { const { integrations, config } = options; - return createTemplateAction<{ - repoUrl: string; - description?: string; - defaultBranch?: string; - sourcePath?: string; - token?: string; - gitCommitMessage?: string; - gitAuthorName?: string; - gitAuthorEmail?: string; - signCommit?: boolean; - }>({ + return createTemplateAction({ id: 'publish:azure', examples, description: 'Initializes a git repository of the content in the workspace, and publishes it to Azure.', schema: { input: { - type: 'object', - required: ['repoUrl'], - properties: { - repoUrl: { - title: 'Repository Location', - type: 'string', - }, - description: { - title: 'Repository Description', - type: 'string', - }, - defaultBranch: { - title: 'Default Branch', - type: 'string', - description: `Sets the default branch on the repository. The default value is 'master'`, - }, - gitCommitMessage: { - title: 'Git Commit Message', - type: 'string', - description: `Sets the commit message on the repository. The default value is 'initial commit'`, - }, - gitAuthorName: { - title: 'Default Author Name', - type: 'string', - description: `Sets the default author name for the commit. The default value is 'Scaffolder'`, - }, - gitAuthorEmail: { - title: 'Default Author Email', - type: 'string', - description: `Sets the default author email for the commit.`, - }, - sourcePath: { - title: 'Source Path', - description: - 'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.', - type: 'string', - }, - token: { - title: 'Authentication Token', - type: 'string', - description: 'The token to use for authorization to Azure', - }, - signCommit: { - title: 'Sign commit', - type: 'boolean', - description: 'Sign commit with configured PGP private key', - }, - }, + repoUrl: z => + z.string({ + description: 'Repository Location', + }), + description: z => + z + .string({ + description: 'Repository Description', + }) + .optional(), + defaultBranch: z => + z + .string({ + description: `Sets the default branch on the repository. The default value is 'master'`, + }) + .optional(), + sourcePath: z => + z + .string({ + description: + 'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.', + }) + .optional(), + token: z => + z + .string({ + description: 'The token to use for authorization to Azure', + }) + .optional(), + gitCommitMessage: z => + z + .string({ + description: `Sets the commit message on the repository. The default value is 'initial commit'`, + }) + .optional(), + gitAuthorName: z => + z + .string({ + description: `Sets the default author name for the commit. The default value is 'Scaffolder'`, + }) + .optional(), + gitAuthorEmail: z => + z + .string({ + description: `Sets the default author email for the commit.`, + }) + .optional(), + signCommit: z => + z + .boolean({ + description: 'Sign commit with configured PGP private key', + }) + .optional(), }, output: { - type: 'object', - properties: { - remoteUrl: { - title: 'A URL to the repository with the provider', - type: 'string', - }, - repoContentsUrl: { - title: 'A URL to the root of the repository', - type: 'string', - }, - repositoryId: { - title: 'The Id of the created repository', - type: 'string', - }, - commitHash: { - title: 'The git commit hash of the initial commit', - type: 'string', - }, - }, + remoteUrl: z => + z + .string({ + description: 'A URL to the repository with the provider', + }) + .optional(), + repoContentsUrl: z => + z + .string({ + description: 'A URL to the root of the repository', + }) + .optional(), + repositoryId: z => + z + .string({ + description: 'The Id of the created repository', + }) + .optional(), + commitHash: z => + z + .string({ + description: 'The git commit hash of the initial commit', + }) + .optional(), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md index 72fc113229..852df7a904 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket-cloud +## 0.2.10-next.1 + +### Patch Changes + +- ca9fdc0: Migrate `bitbucket-cloud` to new actions format +- 280611d: Fix `bitbucketCloudBranchRestrictions` API calls to accept null to prevent 400 errors for some branch restriction kinds defined. +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/plugin-bitbucket-cloud-common@0.3.0 + +## 0.2.10-next.0 + +### Patch Changes + +- 9c8ff0c: Update pull request creation filter to include .gitignore files in the created pull request +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + ## 0.2.9 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json index ed6d6eeef4..2c4c83f5b6 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud", - "version": "0.2.9", + "version": "0.2.10-next.1", "description": "The Bitbucket Cloud module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", @@ -50,7 +50,8 @@ "@backstage/plugin-scaffolder-node": "workspace:^", "bitbucket": "^2.12.0", "fs-extra": "^11.2.0", - "yaml": "^2.0.0" + "yaml": "^2.0.0", + "zod": "^3.22.4" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md b/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md index 8deda92860..af34609213 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md @@ -5,7 +5,6 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; -import { JsonObject } from '@backstage/types'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; @@ -20,15 +19,56 @@ export const createBitbucketPipelinesRunAction: (options: { { workspace: string; repo_slug: string; - body?: object; - token?: string; + body?: + | { + target?: + | { + type?: string | undefined; + source?: string | undefined; + selector?: + | { + type: string; + pattern: string; + } + | undefined; + pull_request?: + | { + id: string; + } + | undefined; + commit?: + | { + type: string; + hash: string; + } + | undefined; + destination?: string | undefined; + ref_name?: string | undefined; + ref_type?: string | undefined; + destination_commit?: + | { + hash: string; + } + | undefined; + } + | undefined; + variables?: + | { + key: string; + value: string; + secured: boolean; + }[] + | undefined; + } + | undefined; + token?: string | undefined; }, { - buildNumber: number; - repoUrl: string; - pipelinesUrl: string; + buildNumber?: number | undefined; + repoUrl?: string | undefined; + pipelinesUrl?: string | undefined; }, - 'v1' + 'v2' >; // @public @@ -38,16 +78,20 @@ export function createPublishBitbucketCloudAction(options: { }): TemplateAction< { repoUrl: string; - description?: string; - defaultBranch?: string; - repoVisibility?: 'private' | 'public'; - gitCommitMessage?: string; - sourcePath?: string; - token?: string; - signCommit?: boolean; + description?: string | undefined; + defaultBranch?: string | undefined; + repoVisibility?: 'private' | 'public' | undefined; + gitCommitMessage?: string | undefined; + sourcePath?: string | undefined; + token?: string | undefined; + signCommit?: boolean | undefined; }, - JsonObject, - 'v1' + { + remoteUrl?: string | undefined; + repoContentsUrl?: string | undefined; + commitHash?: string | undefined; + }, + 'v2' >; // @public @@ -58,14 +102,16 @@ export function createPublishBitbucketCloudPullRequestAction(options: { { repoUrl: string; title: string; - description?: string; - targetBranch?: string; sourceBranch: string; - token?: string; - gitAuthorName?: string; - gitAuthorEmail?: string; + description?: string | undefined; + targetBranch?: string | undefined; + token?: string | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; }, - JsonObject, - 'v1' + { + pullRequestUrl: string; + }, + 'v2' >; ``` diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts index ff61213e1a..722bf01303 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts @@ -105,83 +105,81 @@ export function createPublishBitbucketCloudAction(options: { }) { const { integrations, config } = options; - return createTemplateAction<{ - repoUrl: string; - description?: string; - defaultBranch?: string; - repoVisibility?: 'private' | 'public'; - gitCommitMessage?: string; - sourcePath?: string; - token?: string; - signCommit?: boolean; - }>({ + return createTemplateAction({ id: 'publish:bitbucketCloud', examples, description: 'Initializes a git repository of the content in the workspace, and publishes it to Bitbucket Cloud.', schema: { input: { - type: 'object', - required: ['repoUrl'], - properties: { - repoUrl: { - title: 'Repository Location', - type: 'string', - }, - description: { - title: 'Repository Description', - type: 'string', - }, - repoVisibility: { - title: 'Repository Visibility', - type: 'string', - enum: ['private', 'public'], - }, - defaultBranch: { - title: 'Default Branch', - type: 'string', - description: `Sets the default branch on the repository. The default value is 'master'`, - }, - gitCommitMessage: { - title: 'Git Commit Message', - type: 'string', - description: `Sets the commit message on the repository. The default value is 'initial commit'`, - }, - sourcePath: { - title: 'Source Path', - description: - 'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.', - type: 'string', - }, - token: { - title: 'Authentication Token', - type: 'string', - description: - 'The token to use for authorization to BitBucket Cloud', - }, - signCommit: { - title: 'Sign commit', - type: 'boolean', - description: 'Sign commit with configured PGP private key', - }, - }, + repoUrl: z => + z.string({ + description: 'Repository Location', + }), + description: z => + z + .string({ + description: 'Repository Description', + }) + .optional(), + defaultBranch: z => + z + .string({ + description: `Sets the default branch on the repository. The default value is 'master'`, + }) + .optional(), + repoVisibility: z => + z + .enum(['private', 'public'], { + description: 'Repository Visibility', + }) + .optional(), + gitCommitMessage: z => + z + .string({ + description: `Sets the commit message on the repository. The default value is 'initial commit'`, + }) + .optional(), + sourcePath: z => + z + .string({ + description: + 'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.', + }) + .optional(), + token: z => + z + .string({ + description: + 'The token to use for authorization to BitBucket Cloud', + }) + .optional(), + signCommit: z => + z + .boolean({ + description: 'Sign commit with configured PGP private key', + }) + .optional(), }, output: { - type: 'object', - properties: { - remoteUrl: { - title: 'A URL to the repository with the provider', - type: 'string', - }, - repoContentsUrl: { - title: 'A URL to the root of the repository', - type: 'string', - }, - commitHash: { - title: 'The git commit hash of the initial commit', - type: 'string', - }, - }, + remoteUrl: z => + z + .string({ + description: 'A URL to the repository with the provider', + }) + .optional(), + repoContentsUrl: z => + z + .string({ + description: 'A URL to the root of the repository', + }) + .optional(), + commitHash: z => + z + .string({ + description: 'The git commit hash of the initial commit', + }) + .optional(), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.ts index d63ccf1b7c..233818e00a 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.ts @@ -30,7 +30,7 @@ const createBitbucketCloudBranchRestriction = async (opts: { branchMatchKind?: string; branchType?: string; pattern?: string; - value?: number; + value?: number | null; users?: { uuid: string; type: string }[]; groups?: { slug: string; type: string }[]; authorization: { @@ -78,49 +78,36 @@ export function createBitbucketCloudBranchRestrictionAction(options: { integrations: ScmIntegrationRegistry; }) { const { integrations } = options; - return createTemplateAction<{ - repoUrl: string; - kind: string; - branchMatchKind?: string; - branchType?: string; - pattern?: string; - value?: number; - users?: { uuid: string }[]; - groups?: { slug: string }[]; - token?: string; - }>({ + return createTemplateAction({ id: 'bitbucketCloud:branchRestriction:create', examples, description: 'Creates branch restrictions for a Bitbucket Cloud repository.', schema: { input: { - type: 'object', - required: ['repoUrl', 'kind'], - properties: { - repoUrl: inputProps.repoUrl, - kind: inputProps.restriction.kind, - branchMatchKind: inputProps.restriction.branchMatchKind, - branchType: inputProps.restriction.branchType, - pattern: inputProps.restriction.pattern, - value: inputProps.restriction.value, - users: inputProps.restriction.users, - groups: inputProps.restriction.groups, - token: inputProps.token, - }, + repoUrl: inputProps.repoUrl, + kind: inputProps.restriction.kind, + branchMatchKind: inputProps.restriction.branchMatchKind, + branchType: inputProps.restriction.branchType, + pattern: inputProps.restriction.pattern, + value: inputProps.restriction.value, + users: inputProps.restriction.users, + groups: inputProps.restriction.groups, + token: inputProps.token, }, output: { - type: 'object', - properties: { - json: { - title: 'The response from bitbucket cloud', - type: 'string', - }, - statusCode: { - title: 'The status code of the response', - type: 'number', - }, - }, + json: z => + z + .string({ + description: 'The response from bitbucket cloud', + }) + .optional(), + statusCode: z => + z + .number({ + description: 'The status code of the response', + }) + .optional(), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.ts index 11064f4743..4bf9226db7 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.ts @@ -30,49 +30,36 @@ export const createBitbucketPipelinesRunAction = (options: { integrations: ScmIntegrationRegistry; }) => { const { integrations } = options; - return createTemplateAction< - { - workspace: string; - repo_slug: string; - body?: object; - token?: string; - }, - { - buildNumber: number; - repoUrl: string; - pipelinesUrl: string; - } - >({ + return createTemplateAction({ id, description: 'Run a bitbucket cloud pipeline', examples, schema: { input: { - type: 'object', - required: ['workspace', 'repo_slug'], - properties: { - workspace: inputProps.workspace, - repo_slug: inputProps.repo_slug, - body: inputProps.pipelinesRunBody, - token: inputProps.token, - }, + workspace: inputProps.workspace, + repo_slug: inputProps.repo_slug, + body: inputProps.pipelinesRunBody, + token: inputProps.token, }, output: { - type: 'object', - properties: { - buildNumber: { - title: 'Build number', - type: 'number', - }, - repoUrl: { - title: 'A URL to the pipeline repository', - type: 'string', - }, - repoContentsUrl: { - title: 'A URL to the pipeline', - type: 'string', - }, - }, + buildNumber: z => + z + .number({ + description: 'Build number', + }) + .optional(), + repoUrl: z => + z + .string({ + description: 'A URL to the pipeline repository', + }) + .optional(), + pipelinesUrl: z => + z + .string({ + description: 'A URL to the pipeline', + }) + .optional(), }, }, supportsDryRun: false, diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPullRequest.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPullRequest.ts index eba9c74a6a..9969f13eb2 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPullRequest.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPullRequest.ts @@ -23,6 +23,7 @@ import { addFiles, cloneRepo, parseRepoUrl, + isNotGitDirectoryOrContents, } from '@backstage/plugin-scaffolder-node'; import { Config } from '@backstage/config'; import fs from 'fs-extra'; @@ -138,6 +139,7 @@ const findBranches = async (opts: { return r.values[0]; }; + const createBranch = async (opts: { workspace: string; repo: string; @@ -189,6 +191,7 @@ const createBranch = async (opts: { return await response.json(); }; + const getDefaultBranch = async (opts: { workspace: string; repo: string; @@ -232,73 +235,60 @@ export function createPublishBitbucketCloudPullRequestAction(options: { }) { const { integrations, config } = options; - return createTemplateAction<{ - repoUrl: string; - title: string; - description?: string; - targetBranch?: string; - sourceBranch: string; - token?: string; - gitAuthorName?: string; - gitAuthorEmail?: string; - }>({ + return createTemplateAction({ id: 'publish:bitbucketCloud:pull-request', examples, schema: { input: { - type: 'object', - required: ['repoUrl', 'title', 'sourceBranch'], - properties: { - repoUrl: { - title: 'Repository Location', - type: 'string', - }, - title: { - title: 'Pull Request title', - type: 'string', + repoUrl: z => + z.string({ + description: 'Repository Location', + }), + title: z => + z.string({ description: 'The title for the pull request', - }, - description: { - title: 'Pull Request Description', - type: 'string', - description: 'The description of the pull request', - }, - targetBranch: { - title: 'Target Branch', - type: 'string', - description: `Branch of repository to apply changes to. The default value is 'master'`, - }, - sourceBranch: { - title: 'Source Branch', - type: 'string', + }), + description: z => + z + .string({ + description: 'The description of the pull request', + }) + .optional(), + targetBranch: z => + z + .string({ + description: `Branch of repository to apply changes to. The default value is 'master'`, + }) + .optional(), + sourceBranch: z => + z.string({ description: 'Branch of repository to copy changes from', - }, - token: { - title: 'Authorization Token', - type: 'string', - description: - 'The token to use for authorization to BitBucket Cloud', - }, - gitAuthorName: { - title: 'Author Name', - type: 'string', - description: `Sets the author name for the commit. The default value is 'Scaffolder'`, - }, - gitAuthorEmail: { - title: 'Author Email', - type: 'string', - description: `Sets the author email for the commit.`, - }, - }, + }), + token: z => + z + .string({ + description: + 'The token to use for authorization to BitBucket Cloud', + }) + .optional(), + gitAuthorName: z => + z + .string({ + description: `Sets the author name for the commit. The default value is 'Scaffolder'`, + }) + .optional(), + gitAuthorEmail: z => + z + .string({ + description: `Sets the author email for the commit.`, + }) + .optional(), }, output: { - type: 'object', - properties: { - pullRequestUrl: { - title: 'A URL to the pull request with the provider', - type: 'string', - }, - }, + pullRequestUrl: z => + z.string({ + description: 'A URL to the pull request with the provider', + }), }, }, async handler(ctx) { @@ -413,9 +403,7 @@ export function createPublishBitbucketCloudPullRequestAction(options: { // copy files fs.cpSync(sourceDir, tempDir, { recursive: true, - filter: path => { - return !(path.indexOf('.git') > -1); - }, + filter: isNotGitDirectoryOrContents, }); await addFiles({ diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/inputProperties.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/inputProperties.ts index cff1457463..fadc8bcefb 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/inputProperties.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/inputProperties.ts @@ -14,238 +14,224 @@ * limitations under the License. */ -const repoUrl = { - title: 'Repository Location', - description: `Accepts the format 'bitbucket.org?repo=reponame&workspace=workspace&project=project' where 'reponame' is the new repository name`, - type: 'string', -}; +import { z as zod } from 'zod'; -const workspace = { - title: 'Workspace', - description: `The workspace name`, - type: 'string', -}; +const repoUrl = (z: typeof zod) => + z.string({ + description: `Accepts the format 'bitbucket.org?repo=reponame&workspace=workspace&project=project' where 'reponame' is the new repository name`, + }); -const repo_slug = { - title: 'Repository name', - description: 'The repository name', - type: 'string', -}; +const workspace = (z: typeof zod) => + z.string({ + description: 'The workspace name', + }); -const ref_type = { - title: 'ref_type', - type: 'string', -}; +const repo_slug = (z: typeof zod) => + z.string({ + description: 'The repository name', + }); -const type = { - title: 'type', - type: 'string', -}; +const ref_type = (z: typeof zod) => + z.string({ + description: 'ref_type', + }); -const ref_name = { - title: 'ref_name', - type: 'string', -}; -const source = { - title: 'source', - type: 'string', -}; -const destination = { - title: 'destination', - type: 'string', -}; -const hash = { - title: 'hash', - type: 'string', -}; +const type = (z: typeof zod) => + z.string({ + description: 'type', + }); -const pattern = { - title: 'pattern', - type: 'string', -}; +const ref_name = (z: typeof zod) => + z.string({ + description: 'ref_name', + }); -const id = { - title: 'id', - type: 'string', -}; +const source = (z: typeof zod) => + z.string({ + description: 'source', + }); -const key = { - title: 'key', - type: 'string', -}; -const value = { - title: 'value', - type: 'string', -}; -const secured = { - title: 'secured', - type: 'boolean', -}; +const destination = (z: typeof zod) => + z.string({ + description: 'destination', + }); -const token = { - title: 'Authentication Token', - type: 'string', - description: 'The token to use for authorization to BitBucket Cloud', -}; +const hash = (z: typeof zod) => + z.string({ + description: 'hash', + }); -const destination_commit = { - title: 'destination_commit', - type: 'object', - properties: { - hash, - }, -}; +const pattern = (z: typeof zod) => + z.string({ + description: 'pattern', + }); -const commit = { - title: 'commit', - type: 'object', - properties: { - type, - hash, - }, -}; +const id = (z: typeof zod) => + z.string({ + description: 'id', + }); -const selector = { - title: 'selector', - type: 'object', - properties: { - type, - pattern, - }, -}; +const key = (z: typeof zod) => + z.string({ + description: 'key', + }); -const pull_request = { - title: 'pull_request', - type: 'object', - properties: { - id, - }, -}; +const value = (z: typeof zod) => + z.string({ + description: 'value', + }); -const pipelinesRunBody = { - title: 'Request Body', - description: - 'Request body properties: see Bitbucket Cloud Rest API documentation for more details', - type: 'object', - properties: { - target: { - title: 'target', - type: 'object', - properties: { - ref_type, - type, - ref_name, - source, - destination, - destination_commit, - commit, - selector, - pull_request, +const secured = (z: typeof zod) => + z.boolean({ + description: 'secured', + }); + +const token = (z: typeof zod) => + z + .string({ + description: 'The token to use for authorization to BitBucket Cloud', + }) + .optional(); + +const destination_commit = (z: typeof zod) => + z.object({ + hash: hash(z), + }); + +const commit = (z: typeof zod) => + z.object({ + type: type(z), + hash: hash(z), + }); + +const selector = (z: typeof zod) => + z.object({ + type: type(z), + pattern: pattern(z), + }); + +const pull_request = (z: typeof zod) => + z.object({ + id: id(z), + }); + +const pipelinesRunBody = (z: typeof zod) => + z + .object( + { + target: z + .object({ + ref_type: ref_type(z).optional(), + type: type(z).optional(), + ref_name: ref_name(z).optional(), + source: source(z).optional(), + destination: destination(z).optional(), + destination_commit: destination_commit(z).optional(), + commit: commit(z).optional(), + selector: selector(z).optional(), + pull_request: pull_request(z).optional(), + }) + .optional(), + variables: z + .array( + z.object({ + key: key(z), + value: value(z), + secured: secured(z), + }), + ) + .optional(), }, - }, - variables: { - title: 'variables', - type: 'array', - items: { - type: 'object', - properties: { - key, - value, - secured, - }, + { + description: + 'Request body properties: see Bitbucket Cloud Rest API documentation for more details', }, - }, - }, -}; + ) + .optional(); const restriction = { - kind: { - title: 'kind', - description: 'The kind of restriction.', - type: 'string', - enum: [ - 'push', - 'force', - 'delete', - 'restrict_merges', - 'require_tasks_to_be_completed', - 'require_approvals_to_merge', - 'require_default_reviewer_approvals_to_merge', - 'require_no_changes_requested', - 'require_passing_builds_to_merge', - 'require_commits_behind', - 'reset_pullrequest_approvals_on_change', - 'smart_reset_pullrequest_approvals', - 'reset_pullrequest_changes_requested_on_change', - 'require_all_dependencies_merged', - 'enforce_merge_checks', - 'allow_auto_merge_when_builds_pass', - ], - }, - branchMatchKind: { - title: 'branch_match_kind', - description: 'The branch match kind.', - type: 'string', - enum: ['glob', 'branching_model'], - }, - branchType: { - title: 'branch_type', - description: - 'The branch type. When branchMatchKind is set to branching_model, this field is required.', - type: 'string', - enum: [ - 'feature', - 'bugfix', - 'release', - 'hotfix', - 'development', - 'production', - ], - }, - pattern: { - title: 'pattern', - description: - 'The pattern to match branches against. This field is required when branchMatchKind is set to glob.', - type: 'string', - }, - value: { - title: 'value', - description: - 'The value of the restriction. This field is required when kind is one of require_approvals_to_merge / require_default_reviewer_approvals_to_merge / require_passing_builds_to_merge / require_commits_behind.', - type: 'number', - }, - users: { - title: 'users', - description: - 'Names of users that can bypass the push / restrict_merges restriction kind. For any other kind, this field will be ignored.', - type: 'array', - items: { - type: 'object', - properties: { - uuid: { - title: 'uuid', - description: 'The UUID of the user in the format "{a-b-c-d}".', - type: 'string', - }, + kind: (z: typeof zod) => + z.enum( + [ + 'push', + 'force', + 'delete', + 'restrict_merges', + 'require_tasks_to_be_completed', + 'require_approvals_to_merge', + 'require_default_reviewer_approvals_to_merge', + 'require_no_changes_requested', + 'require_passing_builds_to_merge', + 'require_commits_behind', + 'reset_pullrequest_approvals_on_change', + 'smart_reset_pullrequest_approvals', + 'reset_pullrequest_changes_requested_on_change', + 'require_all_dependencies_merged', + 'enforce_merge_checks', + 'allow_auto_merge_when_builds_pass', + ], + { + description: 'The kind of restriction.', }, - }, - }, - groups: { - title: 'groups', - description: - 'Names of groups that can bypass the push / restrict_merges restriction kind. For any other kind, this field will be ignored.', - type: 'array', - items: { - type: 'object', - properties: { - slug: { - title: 'slug', - description: 'The name of the group.', - type: 'string', + ), + branchMatchKind: (z: typeof zod) => + z + .enum(['glob', 'branching_model'], { + description: 'The branch match kind.', + }) + .optional(), + branchType: (z: typeof zod) => + z + .enum( + ['feature', 'bugfix', 'release', 'hotfix', 'development', 'production'], + { + description: + 'The branch type. When branchMatchKind is set to branching_model, this field is required.', }, - }, - }, - }, + ) + .optional(), + pattern: (z: typeof zod) => + z + .string({ + description: + 'The pattern to match branches against. This field is required when branchMatchKind is set to glob.', + }) + .optional(), + value: (z: typeof zod) => + z + .union([z.number(), z.null()], { + description: + 'The value of the restriction. This field is required when kind is one of require_approvals_to_merge / require_default_reviewer_approvals_to_merge / require_passing_builds_to_merge / require_commits_behind.', + }) + .optional(), + users: (z: typeof zod) => + z + .array( + z.object({ + uuid: z.string({ + description: 'The UUID of the user in the format "{a-b-c-d}".', + }), + }), + { + description: + 'Names of users that can bypass the push / restrict_merges restriction kind. For any other kind, this field will be ignored.', + }, + ) + .optional(), + groups: (z: typeof zod) => + z + .array( + z.object({ + slug: z.string({ + description: 'The name of the group.', + }), + }), + { + description: + 'Names of groups that can bypass the push / restrict_merges restriction kind. For any other kind, this field will be ignored.', + }, + ) + .optional(), }; export { workspace, repo_slug, pipelinesRunBody, token }; diff --git a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md index aa9885167a..7d56244457 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket-server +## 0.2.10-next.1 + +### Patch Changes + +- d8169fc: Migrate the actions to the new format +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + +## 0.2.10-next.0 + +### Patch Changes + +- 9c8ff0c: Update pull request creation filter to include .gitignore files in the created pull request +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + ## 0.2.9 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket-server/package.json b/plugins/scaffolder-backend-module-bitbucket-server/package.json index 65f4eba9dc..5a6cf1f317 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/package.json +++ b/plugins/scaffolder-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket-server", - "version": "0.2.9", + "version": "0.2.10-next.1", "description": "The Bitbucket Server module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", @@ -48,7 +48,8 @@ "@backstage/integration": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", "fs-extra": "^11.2.0", - "yaml": "^2.0.0" + "yaml": "^2.0.0", + "zod": "^3.22.4" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/plugins/scaffolder-backend-module-bitbucket-server/report.api.md b/plugins/scaffolder-backend-module-bitbucket-server/report.api.md index 2f4d68c95d..929eb165c4 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/report.api.md +++ b/plugins/scaffolder-backend-module-bitbucket-server/report.api.md @@ -5,7 +5,6 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; -import { JsonObject } from '@backstage/types'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; @@ -20,19 +19,23 @@ export function createPublishBitbucketServerAction(options: { }): TemplateAction< { repoUrl: string; - description?: string; - defaultBranch?: string; - repoVisibility?: 'private' | 'public'; - sourcePath?: string; - enableLFS?: boolean; - token?: string; - gitCommitMessage?: string; - gitAuthorName?: string; - gitAuthorEmail?: string; - signCommit?: boolean; + description?: string | undefined; + repoVisibility?: 'private' | 'public' | undefined; + defaultBranch?: string | undefined; + sourcePath?: string | undefined; + enableLFS?: boolean | undefined; + token?: string | undefined; + gitCommitMessage?: string | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; + signCommit?: boolean | undefined; }, - JsonObject, - 'v1' + { + remoteUrl?: string | undefined; + repoContentsUrl?: string | undefined; + commitHash?: string | undefined; + }, + 'v2' >; // @public @@ -43,15 +46,17 @@ export function createPublishBitbucketServerPullRequestAction(options: { { repoUrl: string; title: string; - description?: string; - targetBranch?: string; sourceBranch: string; - reviewers?: string[]; - token?: string; - gitAuthorName?: string; - gitAuthorEmail?: string; + description?: string | undefined; + targetBranch?: string | undefined; + reviewers?: string[] | undefined; + token?: string | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; }, - JsonObject, - 'v1' + { + pullRequestUrl?: string | undefined; + }, + 'v2' >; ``` diff --git a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServer.ts b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServer.ts index 3f50d5f274..98dcaa353c 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServer.ts +++ b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServer.ts @@ -126,101 +126,99 @@ export function createPublishBitbucketServerAction(options: { }) { const { integrations, config } = options; - return createTemplateAction<{ - repoUrl: string; - description?: string; - defaultBranch?: string; - repoVisibility?: 'private' | 'public'; - sourcePath?: string; - enableLFS?: boolean; - token?: string; - gitCommitMessage?: string; - gitAuthorName?: string; - gitAuthorEmail?: string; - signCommit?: boolean; - }>({ + return createTemplateAction({ id: 'publish:bitbucketServer', description: 'Initializes a git repository of the content in the workspace, and publishes it to Bitbucket Server.', examples, schema: { input: { - type: 'object', - required: ['repoUrl'], - properties: { - repoUrl: { - title: 'Repository Location', - type: 'string', - }, - description: { - title: 'Repository Description', - type: 'string', - }, - repoVisibility: { - title: 'Repository Visibility', - type: 'string', - enum: ['private', 'public'], - }, - defaultBranch: { - title: 'Default Branch', - type: 'string', - description: `Sets the default branch on the repository. The default value is 'master'`, - }, - sourcePath: { - title: 'Source Path', - description: - 'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.', - type: 'string', - }, - enableLFS: { - title: 'Enable LFS?', - description: 'Enable LFS for the repository.', - type: 'boolean', - }, - token: { - title: 'Authentication Token', - type: 'string', - description: - 'The token to use for authorization to BitBucket Server', - }, - gitCommitMessage: { - title: 'Git Commit Message', - type: 'string', - description: `Sets the commit message on the repository. The default value is 'initial commit'`, - }, - gitAuthorName: { - title: 'Author Name', - type: 'string', - description: `Sets the author name for the commit. The default value is 'Scaffolder'`, - }, - gitAuthorEmail: { - title: 'Author Email', - type: 'string', - description: `Sets the author email for the commit.`, - }, - signCommit: { - title: 'Sign commit', - type: 'boolean', - description: 'Sign commit with configured PGP private key', - }, - }, + repoUrl: z => + z.string({ + description: 'Repository Location', + }), + description: z => + z + .string({ + description: 'Repository Description', + }) + .optional(), + repoVisibility: z => + z + .enum(['private', 'public'], { + description: 'Repository Visibility', + }) + .optional(), + defaultBranch: z => + z + .string({ + description: `Sets the default branch on the repository. The default value is 'master'`, + }) + .optional(), + sourcePath: z => + z + .string({ + description: + 'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.', + }) + .optional(), + enableLFS: z => + z + .boolean({ + description: 'Enable LFS for the repository.', + }) + .optional(), + token: z => + z + .string({ + description: + 'The token to use for authorization to BitBucket Server', + }) + .optional(), + gitCommitMessage: z => + z + .string({ + description: `Sets the commit message on the repository. The default value is 'initial commit'`, + }) + .optional(), + gitAuthorName: z => + z + .string({ + description: `Sets the author name for the commit. The default value is 'Scaffolder'`, + }) + .optional(), + gitAuthorEmail: z => + z + .string({ + description: `Sets the author email for the commit.`, + }) + .optional(), + signCommit: z => + z + .boolean({ + description: 'Sign commit with configured PGP private key', + }) + .optional(), }, output: { - type: 'object', - properties: { - remoteUrl: { - title: 'A URL to the repository with the provider', - type: 'string', - }, - repoContentsUrl: { - title: 'A URL to the root of the repository', - type: 'string', - }, - commitHash: { - title: 'The git commit hash of the initial commit', - type: 'string', - }, - }, + remoteUrl: z => + z + .string({ + description: 'A URL to the repository with the provider', + }) + .optional(), + repoContentsUrl: z => + z + .string({ + description: 'A URL to the root of the repository', + }) + .optional(), + commitHash: z => + z + .string({ + description: 'The git commit hash of the initial commit', + }) + .optional(), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.ts b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.ts index 71c16beaa7..8717a76ae1 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.ts +++ b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.ts @@ -26,6 +26,7 @@ import { addFiles, cloneRepo, parseRepoUrl, + isNotGitDirectoryOrContents, } from '@backstage/plugin-scaffolder-node'; import { Config } from '@backstage/config'; import fs from 'fs-extra'; @@ -252,83 +253,69 @@ export function createPublishBitbucketServerPullRequestAction(options: { }) { const { integrations, config } = options; - return createTemplateAction<{ - repoUrl: string; - title: string; - description?: string; - targetBranch?: string; - sourceBranch: string; - reviewers?: string[]; - token?: string; - gitAuthorName?: string; - gitAuthorEmail?: string; - }>({ + return createTemplateAction({ id: 'publish:bitbucketServer:pull-request', examples, schema: { input: { - type: 'object', - required: ['repoUrl', 'title', 'sourceBranch'], - properties: { - repoUrl: { - title: 'Repository Location', - type: 'string', - }, - title: { - title: 'Pull Request title', - type: 'string', + repoUrl: z => + z.string({ + description: 'Repository Location', + }), + title: z => + z.string({ description: 'The title for the pull request', - }, - description: { - title: 'Pull Request Description', - type: 'string', - description: 'The description of the pull request', - }, - targetBranch: { - title: 'Target Branch', - type: 'string', - description: `Branch of repository to apply changes to. The default value is 'master'`, - }, - sourceBranch: { - title: 'Source Branch', - type: 'string', + }), + description: z => + z + .string({ + description: 'The description of the pull request', + }) + .optional(), + targetBranch: z => + z + .string({ + description: `Branch of repository to apply changes to. The default value is 'master'`, + }) + .optional(), + sourceBranch: z => + z.string({ description: 'Branch of repository to copy changes from', - }, - reviewers: { - title: 'Pull Request Reviewers', - type: 'array', - items: { - type: 'string', - }, - description: - 'The usernames of reviewers that will be added to the pull request', - }, - token: { - title: 'Authorization Token', - type: 'string', - description: - 'The token to use for authorization to BitBucket Server', - }, - gitAuthorName: { - title: 'Author Name', - type: 'string', - description: `Sets the author name for the commit. The default value is 'Scaffolder'`, - }, - gitAuthorEmail: { - title: 'Author Email', - type: 'string', - description: `Sets the author email for the commit.`, - }, - }, + }), + reviewers: z => + z + .array(z.string(), { + description: + 'The usernames of reviewers that will be added to the pull request', + }) + .optional(), + token: z => + z + .string({ + description: + 'The token to use for authorization to BitBucket Server', + }) + .optional(), + gitAuthorName: z => + z + .string({ + description: `Sets the author name for the commit. The default value is 'Scaffolder'`, + }) + .optional(), + gitAuthorEmail: z => + z + .string({ + description: `Sets the author email for the commit.`, + }) + .optional(), }, output: { - type: 'object', - properties: { - pullRequestUrl: { - title: 'A URL to the pull request with the provider', - type: 'string', - }, - }, + pullRequestUrl: z => + z + .string({ + description: 'A URL to the pull request with the provider', + }) + .optional(), }, }, async handler(ctx) { @@ -453,9 +440,7 @@ export function createPublishBitbucketServerPullRequestAction(options: { // copy files fs.cpSync(sourceDir, tempDir, { recursive: true, - filter: path => { - return !(path.indexOf('.git') > -1); - }, + filter: isNotGitDirectoryOrContents, }); await addFiles({ diff --git a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md index 98be0a453a..0f55793ec6 100644 --- a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket +## 0.3.11-next.1 + +### Patch Changes + +- 7f710d2: Migrating `bitbucket` actions to use the new `zod` format +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.10-next.1 + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.10-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + +## 0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.10-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.10-next.0 + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + ## 0.3.10 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket/package.json b/plugins/scaffolder-backend-module-bitbucket/package.json index 60edbea938..ef8ecb4e7c 100644 --- a/plugins/scaffolder-backend-module-bitbucket/package.json +++ b/plugins/scaffolder-backend-module-bitbucket/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket", - "version": "0.3.10", + "version": "0.3.11-next.1", "description": "The bitbucket module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-bitbucket/report.api.md b/plugins/scaffolder-backend-module-bitbucket/report.api.md index dd45ae5c01..33c9bcc9bd 100644 --- a/plugins/scaffolder-backend-module-bitbucket/report.api.md +++ b/plugins/scaffolder-backend-module-bitbucket/report.api.md @@ -7,7 +7,6 @@ import { BackendFeature } from '@backstage/backend-plugin-api'; import * as bitbucketCloud from '@backstage/plugin-scaffolder-backend-module-bitbucket-cloud'; import * as bitbucketServer from '@backstage/plugin-scaffolder-backend-module-bitbucket-server'; import { Config } from '@backstage/config'; -import { JsonObject } from '@backstage/types'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; @@ -22,15 +21,56 @@ export const createBitbucketPipelinesRunAction: (options: { { workspace: string; repo_slug: string; - body?: object; - token?: string; + body?: + | { + target?: + | { + type?: string | undefined; + source?: string | undefined; + selector?: + | { + type: string; + pattern: string; + } + | undefined; + pull_request?: + | { + id: string; + } + | undefined; + commit?: + | { + type: string; + hash: string; + } + | undefined; + destination?: string | undefined; + ref_name?: string | undefined; + ref_type?: string | undefined; + destination_commit?: + | { + hash: string; + } + | undefined; + } + | undefined; + variables?: + | { + key: string; + value: string; + secured: boolean; + }[] + | undefined; + } + | undefined; + token?: string | undefined; }, { - buildNumber: number; - repoUrl: string; - pipelinesUrl: string; + buildNumber?: number | undefined; + repoUrl?: string | undefined; + pipelinesUrl?: string | undefined; }, - 'v1' + 'v2' >; // @public @deprecated @@ -40,19 +80,23 @@ export function createPublishBitbucketAction(options: { }): TemplateAction< { repoUrl: string; - description?: string; - defaultBranch?: string; - repoVisibility?: 'private' | 'public'; - sourcePath?: string; - enableLFS?: boolean; - token?: string; - gitCommitMessage?: string; - gitAuthorName?: string; - gitAuthorEmail?: string; - signCommit?: boolean; + description?: string | undefined; + repoVisibility?: 'private' | 'public' | undefined; + defaultBranch?: string | undefined; + sourcePath?: string | undefined; + enableLFS?: boolean | undefined; + token?: string | undefined; + gitCommitMessage?: string | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; + signCommit?: boolean | undefined; }, - JsonObject, - 'v1' + { + remoteUrl?: string | undefined; + repoContentsUrl?: string | undefined; + commitHash?: string | undefined; + }, + 'v2' >; // @public @deprecated (undocumented) diff --git a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.ts index e2978470bd..f25bc0a7f1 100644 --- a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.ts +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.ts @@ -209,101 +209,99 @@ export function createPublishBitbucketAction(options: { }) { const { integrations, config } = options; - return createTemplateAction<{ - repoUrl: string; - description?: string; - defaultBranch?: string; - repoVisibility?: 'private' | 'public'; - sourcePath?: string; - enableLFS?: boolean; - token?: string; - gitCommitMessage?: string; - gitAuthorName?: string; - gitAuthorEmail?: string; - signCommit?: boolean; - }>({ + return createTemplateAction({ id: 'publish:bitbucket', description: 'Initializes a git repository of the content in the workspace, and publishes it to Bitbucket.', examples, schema: { input: { - type: 'object', - required: ['repoUrl'], - properties: { - repoUrl: { - title: 'Repository Location', - type: 'string', - }, - description: { - title: 'Repository Description', - type: 'string', - }, - repoVisibility: { - title: 'Repository Visibility', - type: 'string', - enum: ['private', 'public'], - }, - defaultBranch: { - title: 'Default Branch', - type: 'string', - description: `Sets the default branch on the repository. The default value is 'master'`, - }, - sourcePath: { - title: 'Source Path', - description: - 'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.', - type: 'string', - }, - enableLFS: { - title: 'Enable LFS?', - description: - 'Enable LFS for the repository. Only available for hosted Bitbucket.', - type: 'boolean', - }, - token: { - title: 'Authentication Token', - type: 'string', - description: 'The token to use for authorization to BitBucket', - }, - gitCommitMessage: { - title: 'Git Commit Message', - type: 'string', - description: `Sets the commit message on the repository. The default value is 'initial commit'`, - }, - gitAuthorName: { - title: 'Default Author Name', - type: 'string', - description: `Sets the default author name for the commit. The default value is 'Scaffolder'`, - }, - gitAuthorEmail: { - title: 'Default Author Email', - type: 'string', - description: `Sets the default author email for the commit.`, - }, - signCommit: { - title: 'Sign commit', - type: 'boolean', - description: 'Sign commit with configured PGP private key', - }, - }, + repoUrl: z => + z.string({ + description: 'Repository Location', + }), + description: z => + z + .string({ + description: 'Repository Description', + }) + .optional(), + repoVisibility: z => + z + .enum(['private', 'public'], { + description: 'Repository Visibility', + }) + .optional(), + defaultBranch: z => + z + .string({ + description: `Sets the default branch on the repository. The default value is 'master'`, + }) + .optional(), + sourcePath: z => + z + .string({ + description: + 'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.', + }) + .optional(), + enableLFS: z => + z + .boolean({ + description: + 'Enable LFS for the repository. Only available for hosted Bitbucket.', + }) + .optional(), + token: z => + z + .string({ + description: 'The token to use for authorization to BitBucket', + }) + .optional(), + gitCommitMessage: z => + z + .string({ + description: `Sets the commit message on the repository. The default value is 'initial commit'`, + }) + .optional(), + gitAuthorName: z => + z + .string({ + description: `Sets the default author name for the commit. The default value is 'Scaffolder'`, + }) + .optional(), + gitAuthorEmail: z => + z + .string({ + description: `Sets the default author email for the commit.`, + }) + .optional(), + signCommit: z => + z + .boolean({ + description: 'Sign commit with configured PGP private key', + }) + .optional(), }, output: { - type: 'object', - properties: { - remoteUrl: { - title: 'A URL to the repository with the provider', - type: 'string', - }, - repoContentsUrl: { - title: 'A URL to the root of the repository', - type: 'string', - }, - commitHash: { - title: 'The git commit hash of the initial commit', - type: 'string', - }, - }, + remoteUrl: z => + z + .string({ + description: 'A URL to the repository with the provider', + }) + .optional(), + repoContentsUrl: z => + z + .string({ + description: 'A URL to the root of the repository', + }) + .optional(), + commitHash: z => + z + .string({ + description: 'The git commit hash of the initial commit', + }) + .optional(), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md index 8759807985..50ba4a5f6c 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-scaffolder-backend-module-confluence-to-markdown +## 0.3.10-next.1 + +### Patch Changes + +- 4a86bca: Migrate actions to new format +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + +## 0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + ## 0.3.9 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json index 217f561420..e95d610f48 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown", - "version": "0.3.9", + "version": "0.3.10-next.1", "description": "The confluence-to-markdown module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/report.api.md b/plugins/scaffolder-backend-module-confluence-to-markdown/report.api.md index 64584ad4b5..1a3330c135 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/report.api.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/report.api.md @@ -5,7 +5,6 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; -import { JsonObject } from '@backstage/types'; import { ScmIntegrations } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; import { UrlReaderService } from '@backstage/backend-plugin-api'; @@ -24,7 +23,10 @@ export const createConfluenceToMarkdownAction: (options: { confluenceUrls: string[]; repoUrl: string; }, - JsonObject, - 'v1' + { + repo?: string | undefined; + owner?: string | undefined; + }, + 'v2' >; ``` diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts index aa9f94988a..63fa738a19 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts @@ -49,33 +49,36 @@ export const createConfluenceToMarkdownAction = (options: { [key: string]: string; }; - return createTemplateAction<{ - confluenceUrls: string[]; - repoUrl: string; - }>({ + return createTemplateAction({ id: 'confluence:transform:markdown', description: 'Transforms Confluence content to Markdown', examples, schema: { input: { - properties: { - confluenceUrls: { - type: 'array', - title: 'Confluence URL', + confluenceUrls: z => + z.array(z.string(), { description: 'Paste your Confluence url. Ensure it follows this format: https://{confluence+base+url}/display/{spacekey}/{page+title} or https://{confluence+base+url}/spaces/{spacekey}/pages/1234567/{page+title} for Confluence Cloud', - items: { - type: 'string', - default: 'Confluence URL', - }, - }, - repoUrl: { - type: 'string', - title: 'GitHub Repo Url', + }), + repoUrl: z => + z.string({ description: 'mkdocs.yml file location inside the github repo you want to store the document', - }, - }, + }), + }, + output: { + repo: z => + z + .string({ + description: 'Repository name', + }) + .optional(), + owner: z => + z + .string({ + description: 'Repository owner', + }) + .optional(), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md index 296ac4cf24..721b05c601 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-scaffolder-backend-module-cookiecutter +## 0.3.11-next.1 + +### Patch Changes + +- 0b2bbf1: Migrate using new actions format +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/backend-defaults@0.10.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/types@1.2.1 + +## 0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/backend-defaults@0.10.1-next.0 + ## 0.3.10 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index b6677adc89..21b95c0641 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-cookiecutter", - "version": "0.3.10", + "version": "0.3.11-next.1", "description": "A module for the scaffolder backend that lets you template projects using cookiecutter", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-cookiecutter/report.api.md b/plugins/scaffolder-backend-module-cookiecutter/report.api.md index 4f963ce818..2dfba3f42f 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/report.api.md +++ b/plugins/scaffolder-backend-module-cookiecutter/report.api.md @@ -4,7 +4,6 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; -import { JsonObject } from '@backstage/types'; import { ScmIntegrations } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; import { UrlReaderService } from '@backstage/backend-plugin-api'; @@ -48,13 +47,15 @@ export function createFetchCookiecutterAction(options: { }): TemplateAction< { url: string; - targetPath?: string; - values: JsonObject; - copyWithoutRender?: string[]; - extensions?: string[]; - imageName?: string; + values: Record; + targetPath?: string | undefined; + copyWithoutRender?: string[] | undefined; + extensions?: string[] | undefined; + imageName?: string | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; ``` diff --git a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts index b72e25f349..3a4ee8c3d7 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts +++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts @@ -149,65 +149,50 @@ export function createFetchCookiecutterAction(options: { }) { const { reader, containerRunner, integrations } = options; - return createTemplateAction<{ - url: string; - targetPath?: string; - values: JsonObject; - copyWithoutRender?: string[]; - extensions?: string[]; - imageName?: string; - }>({ + return createTemplateAction({ id: 'fetch:cookiecutter', description: 'Downloads a template from the given URL into the workspace, and runs cookiecutter on it.', examples, schema: { input: { - type: 'object', - required: ['url'], - properties: { - url: { - title: 'Fetch URL', + url: z => + z.string({ description: 'Relative path or absolute URL pointing to the directory tree to fetch', - type: 'string', - }, - targetPath: { - title: 'Target Path', - description: - 'Target path within the working directory to download the contents to.', - type: 'string', - }, - values: { - title: 'Template Values', + }), + targetPath: z => + z + .string({ + description: + 'Target path within the working directory to download the contents to.', + }) + .optional(), + values: z => + z.record(z.unknown(), { description: 'Values to pass on to cookiecutter for templating', - type: 'object', - }, - copyWithoutRender: { - title: 'Copy Without Render', - description: - 'Avoid rendering directories and files in the template', - type: 'array', - items: { - type: 'string', - }, - }, - extensions: { - title: 'Template Extensions', - description: - "Jinja2 extensions to add filters, tests, globals or extend the parser. Extensions must be installed in the container or on the host where Cookiecutter executes. See the contrib directory in Backstage's repo for more information", - type: 'array', - items: { - type: 'string', - }, - }, - imageName: { - title: 'Cookiecutter Docker image', - description: - "Specify a custom Docker image to run cookiecutter, to override the default: 'spotify/backstage-cookiecutter'. This can be used to execute cookiecutter with Template Extensions. Used only when a local cookiecutter is not found.", - type: 'string', - }, - }, + }), + copyWithoutRender: z => + z + .array(z.string(), { + description: + 'Avoid rendering directories and files in the template', + }) + .optional(), + extensions: z => + z + .array(z.string(), { + description: + "Jinja2 extensions to add filters, tests, globals or extend the parser. Extensions must be installed in the container or on the host where Cookiecutter executes. See the contrib directory in Backstage's repo for more information", + }) + .optional(), + imageName: z => + z + .string({ + description: + "Specify a custom Docker image to run cookiecutter, to override the default: 'spotify/backstage-cookiecutter'. This can be used to execute cookiecutter with Template Extensions. Used only when a local cookiecutter is not found.", + }) + .optional(), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-gcp/CHANGELOG.md b/plugins/scaffolder-backend-module-gcp/CHANGELOG.md index 912b8f7bf9..bf9576fd11 100644 --- a/plugins/scaffolder-backend-module-gcp/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gcp/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-scaffolder-backend-module-gcp +## 0.2.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + +## 0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + ## 0.2.9 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gcp/package.json b/plugins/scaffolder-backend-module-gcp/package.json index f099864696..4d1b921556 100644 --- a/plugins/scaffolder-backend-module-gcp/package.json +++ b/plugins/scaffolder-backend-module-gcp/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gcp", - "version": "0.2.9", + "version": "0.2.10-next.1", "description": "The GCP Bucket module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md index b92ac981af..bcf48115c0 100644 --- a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-scaffolder-backend-module-gerrit +## 0.2.10-next.1 + +### Patch Changes + +- e24b29b: Migrating to use new format for actions +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + +## 0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + ## 0.2.9 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gerrit/package.json b/plugins/scaffolder-backend-module-gerrit/package.json index 26a8544dd7..091345a30a 100644 --- a/plugins/scaffolder-backend-module-gerrit/package.json +++ b/plugins/scaffolder-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gerrit", - "version": "0.2.9", + "version": "0.2.10-next.1", "description": "The gerrit module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-gerrit/report.api.md b/plugins/scaffolder-backend-module-gerrit/report.api.md index 937df6f435..1e82303b1e 100644 --- a/plugins/scaffolder-backend-module-gerrit/report.api.md +++ b/plugins/scaffolder-backend-module-gerrit/report.api.md @@ -5,7 +5,6 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; -import { JsonObject } from '@backstage/types'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; @@ -17,15 +16,19 @@ export function createPublishGerritAction(options: { { repoUrl: string; description: string; - defaultBranch?: string; - gitCommitMessage?: string; - gitAuthorName?: string; - gitAuthorEmail?: string; - sourcePath?: string; - signCommit?: boolean; + defaultBranch?: string | undefined; + gitCommitMessage?: string | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; + sourcePath?: string | undefined; + signCommit?: boolean | undefined; }, - JsonObject, - 'v1' + { + remoteUrl?: string | undefined; + repoContentsUrl?: string | undefined; + commitHash?: string | undefined; + }, + 'v2' >; // @public @@ -35,15 +38,18 @@ export function createPublishGerritReviewAction(options: { }): TemplateAction< { repoUrl: string; - branch?: string; - sourcePath?: string; - gitCommitMessage?: string; - gitAuthorName?: string; - gitAuthorEmail?: string; - signCommit?: boolean; + branch?: string | undefined; + sourcePath?: string | undefined; + gitCommitMessage?: string | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; + signCommit?: boolean | undefined; }, - JsonObject, - 'v1' + { + reviewUrl?: string | undefined; + repoContentsUrl?: string | undefined; + }, + 'v2' >; // @public diff --git a/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.ts b/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.ts index 23f28ae934..3cc3c47cd1 100644 --- a/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.ts +++ b/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.ts @@ -91,16 +91,7 @@ export function createPublishGerritAction(options: { }) { const { integrations, config } = options; - return createTemplateAction<{ - repoUrl: string; - description: string; - defaultBranch?: string; - gitCommitMessage?: string; - gitAuthorName?: string; - gitAuthorEmail?: string; - sourcePath?: string; - signCommit?: boolean; - }>({ + return createTemplateAction({ id: 'publish:gerrit', supportsDryRun: true, description: @@ -108,65 +99,70 @@ export function createPublishGerritAction(options: { examples, schema: { input: { - type: 'object', - required: ['repoUrl'], - properties: { - repoUrl: { - title: 'Repository Location', - type: 'string', - }, - description: { - title: 'Repository Description', - type: 'string', - }, - defaultBranch: { - title: 'Default Branch', - type: 'string', - description: `Sets the default branch on the repository. The default value is 'master'`, - }, - gitCommitMessage: { - title: 'Git Commit Message', - type: 'string', - description: `Sets the commit message on the repository. The default value is 'initial commit'`, - }, - gitAuthorName: { - title: 'Default Author Name', - type: 'string', - description: `Sets the default author name for the commit. The default value is 'Scaffolder'`, - }, - gitAuthorEmail: { - title: 'Default Author Email', - type: 'string', - description: `Sets the default author email for the commit.`, - }, - sourcePath: { - title: 'Source Path', - type: 'string', - description: `Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.`, - }, - signCommit: { - title: 'Sign commit', - type: 'boolean', - description: 'Sign commit with configured PGP private key', - }, - }, + repoUrl: z => + z.string({ + description: 'Repository Location', + }), + description: z => + z.string({ + description: 'Repository Description', + }), + defaultBranch: z => + z + .string({ + description: `Sets the default branch on the repository. The default value is 'master'`, + }) + .optional(), + gitCommitMessage: z => + z + .string({ + description: `Sets the commit message on the repository. The default value is 'initial commit'`, + }) + .optional(), + gitAuthorName: z => + z + .string({ + description: `Sets the default author name for the commit. The default value is 'Scaffolder'`, + }) + .optional(), + gitAuthorEmail: z => + z + .string({ + description: `Sets the default author email for the commit.`, + }) + .optional(), + sourcePath: z => + z + .string({ + description: `Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.`, + }) + .optional(), + signCommit: z => + z + .boolean({ + description: 'Sign commit with configured PGP private key', + }) + .optional(), }, output: { - type: 'object', - properties: { - remoteUrl: { - title: 'A URL to the repository with the provider', - type: 'string', - }, - repoContentsUrl: { - title: 'A URL to the root of the repository', - type: 'string', - }, - commitHash: { - title: 'The git commit hash of the initial commit', - type: 'string', - }, - }, + remoteUrl: z => + z + .string({ + description: 'A URL to the repository with the provider', + }) + .optional(), + repoContentsUrl: z => + z + .string({ + description: 'A URL to the root of the repository', + }) + .optional(), + commitHash: z => + z + .string({ + description: 'The git commit hash of the initial commit', + }) + .optional(), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.ts b/plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.ts index b4ab3afef7..5688ba8b7f 100644 --- a/plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.ts +++ b/plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.ts @@ -41,73 +41,68 @@ export function createPublishGerritReviewAction(options: { }) { const { integrations, config } = options; - return createTemplateAction<{ - repoUrl: string; - branch?: string; - sourcePath?: string; - gitCommitMessage?: string; - gitAuthorName?: string; - gitAuthorEmail?: string; - signCommit?: boolean; - }>({ + return createTemplateAction({ id: 'publish:gerrit:review', description: 'Creates a new Gerrit review.', examples, schema: { input: { - type: 'object', - required: ['repoUrl', 'gitCommitMessage'], - properties: { - repoUrl: { - title: 'Repository Location', - type: 'string', - }, - branch: { - title: 'Repository branch', - type: 'string', - description: - 'Branch of the repository the review will be created on', - }, - sourcePath: { - type: 'string', - title: 'Working Subdirectory', - description: - 'Subdirectory of working directory containing the repository', - }, - gitCommitMessage: { - title: 'Git Commit Message', - type: 'string', - description: `Sets the commit message on the repository.`, - }, - gitAuthorName: { - title: 'Default Author Name', - type: 'string', - description: `Sets the default author name for the commit. The default value is 'Scaffolder'`, - }, - gitAuthorEmail: { - title: 'Default Author Email', - type: 'string', - description: `Sets the default author email for the commit.`, - }, - signCommit: { - title: 'Sign commit', - type: 'boolean', - description: 'Sign commit with configured PGP private key', - }, - }, + repoUrl: z => + z.string({ + description: 'Repository Location', + }), + branch: z => + z + .string({ + description: + 'Branch of the repository the review will be created on', + }) + .optional(), + sourcePath: z => + z + .string({ + description: + 'Subdirectory of working directory containing the repository', + }) + .optional(), + gitCommitMessage: z => + z + .string({ + description: `Sets the commit message on the repository.`, + }) + .optional(), + gitAuthorName: z => + z + .string({ + description: `Sets the default author name for the commit. The default value is 'Scaffolder'`, + }) + .optional(), + gitAuthorEmail: z => + z + .string({ + description: `Sets the default author email for the commit.`, + }) + .optional(), + signCommit: z => + z + .boolean({ + description: 'Sign commit with configured PGP private key', + }) + .optional(), }, output: { - type: 'object', - properties: { - reviewUrl: { - title: 'A URL to the review', - type: 'string', - }, - repoContentsUrl: { - title: 'A URL to the root of the repository', - type: 'string', - }, - }, + reviewUrl: z => + z + .string({ + description: 'A URL to the review', + }) + .optional(), + repoContentsUrl: z => + z + .string({ + description: 'A URL to the root of the repository', + }) + .optional(), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md index 77db36a42c..94172f1fdb 100644 --- a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-scaffolder-backend-module-gitea +## 0.2.10-next.1 + +### Patch Changes + +- ed41017: Migrate to new actions format +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + +## 0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + ## 0.2.9 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gitea/package.json b/plugins/scaffolder-backend-module-gitea/package.json index bebdae41e2..545a778646 100644 --- a/plugins/scaffolder-backend-module-gitea/package.json +++ b/plugins/scaffolder-backend-module-gitea/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitea", - "version": "0.2.9", + "version": "0.2.10-next.1", "description": "The gitea module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-gitea/report.api.md b/plugins/scaffolder-backend-module-gitea/report.api.md index dede7f013a..afc8286365 100644 --- a/plugins/scaffolder-backend-module-gitea/report.api.md +++ b/plugins/scaffolder-backend-module-gitea/report.api.md @@ -5,7 +5,6 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; -import { JsonObject } from '@backstage/types'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; @@ -17,16 +16,20 @@ export function createPublishGiteaAction(options: { { repoUrl: string; description: string; - defaultBranch?: string; - repoVisibility?: 'private' | 'public'; - gitCommitMessage?: string; - gitAuthorName?: string; - gitAuthorEmail?: string; - sourcePath?: string; - signCommit?: boolean; + defaultBranch?: string | undefined; + repoVisibility?: 'private' | 'public' | undefined; + gitCommitMessage?: string | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; + sourcePath?: string | undefined; + signCommit?: boolean | undefined; }, - JsonObject, - 'v1' + { + remoteUrl?: string | undefined; + repoContentsUrl?: string | undefined; + commitHash?: string | undefined; + }, + 'v2' >; // @public diff --git a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts index 4321de2167..160725c7bd 100644 --- a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts +++ b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts @@ -171,7 +171,7 @@ async function checkAvailabilityGiteaRepository( owner?: string; repo: string; defaultBranch: string; - ctx: ActionContext; + ctx: ActionContext; }, ) { const startTimestamp = Date.now(); @@ -210,88 +210,83 @@ export function createPublishGiteaAction(options: { }) { const { integrations, config } = options; - return createTemplateAction<{ - repoUrl: string; - description: string; - defaultBranch?: string; - repoVisibility?: 'private' | 'public'; - gitCommitMessage?: string; - gitAuthorName?: string; - gitAuthorEmail?: string; - sourcePath?: string; - signCommit?: boolean; - }>({ + return createTemplateAction({ id: 'publish:gitea', description: 'Initializes a git repository using the content of the workspace, and publishes it to Gitea.', examples, schema: { input: { - type: 'object', - required: ['repoUrl'], - properties: { - repoUrl: { - title: 'Repository Location', - type: 'string', - }, - description: { - title: 'Repository Description', - type: 'string', - }, - defaultBranch: { - title: 'Default Branch', - type: 'string', - description: `Sets the default branch on the repository. The default value is 'main'`, - }, - repoVisibility: { - title: 'Repository Visibility', - description: `Sets the visibility of the repository. The default value is 'public'.`, - type: 'string', - enum: ['private', 'public'], - }, - gitCommitMessage: { - title: 'Git Commit Message', - type: 'string', - description: `Sets the commit message on the repository. The default value is 'initial commit'`, - }, - gitAuthorName: { - title: 'Default Author Name', - type: 'string', - description: `Sets the default author name for the commit. The default value is 'Scaffolder'`, - }, - gitAuthorEmail: { - title: 'Default Author Email', - type: 'string', - description: `Sets the default author email for the commit.`, - }, - sourcePath: { - title: 'Source Path', - type: 'string', - description: `Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.`, - }, - signCommit: { - title: 'Sign commit', - type: 'boolean', - description: 'Sign commit with configured PGP private key', - }, - }, + repoUrl: z => + z.string({ + description: 'Repository Location', + }), + description: z => + z.string({ + description: 'Repository Description', + }), + defaultBranch: z => + z + .string({ + description: `Sets the default branch on the repository. The default value is 'main'`, + }) + .optional(), + repoVisibility: z => + z + .enum(['private', 'public'], { + description: `Sets the visibility of the repository. The default value is 'public'.`, + }) + .optional(), + gitCommitMessage: z => + z + .string({ + description: `Sets the commit message on the repository. The default value is 'initial commit'`, + }) + .optional(), + gitAuthorName: z => + z + .string({ + description: `Sets the default author name for the commit. The default value is 'Scaffolder'`, + }) + .optional(), + gitAuthorEmail: z => + z + .string({ + description: `Sets the default author email for the commit.`, + }) + .optional(), + sourcePath: z => + z + .string({ + description: `Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.`, + }) + .optional(), + signCommit: z => + z + .boolean({ + description: 'Sign commit with configured PGP private key', + }) + .optional(), }, output: { - type: 'object', - properties: { - remoteUrl: { - title: 'A URL to the repository with the provider', - type: 'string', - }, - repoContentsUrl: { - title: 'A URL to the root of the repository', - type: 'string', - }, - commitHash: { - title: 'The git commit hash of the initial commit', - type: 'string', - }, - }, + remoteUrl: z => + z + .string({ + description: 'A URL to the repository with the provider', + }) + .optional(), + repoContentsUrl: z => + z + .string({ + description: 'A URL to the root of the repository', + }) + .optional(), + commitHash: z => + z + .string({ + description: 'The git commit hash of the initial commit', + }) + .optional(), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-github/CHANGELOG.md b/plugins/scaffolder-backend-module-github/CHANGELOG.md index 879619f729..3944c6d88a 100644 --- a/plugins/scaffolder-backend-module-github/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-github/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-scaffolder-backend-module-github +## 0.7.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/types@1.2.1 + +## 0.7.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + ## 0.7.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-github/package.json b/plugins/scaffolder-backend-module-github/package.json index 231c09b5a1..1df39a57be 100644 --- a/plugins/scaffolder-backend-module-github/package.json +++ b/plugins/scaffolder-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-github", - "version": "0.7.1", + "version": "0.7.2-next.1", "description": "The github module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", @@ -43,18 +43,19 @@ }, "dependencies": { "@backstage/backend-plugin-api": "workspace:^", - "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", "@backstage/types": "workspace:^", "@octokit/webhooks": "^10.9.2", "libsodium-wrappers": "^0.7.11", "octokit": "^3.0.0", "octokit-plugin-create-pull-request": "^5.0.0", - "yaml": "^2.0.0" + "yaml": "^2.0.0", + "zod": "^3.22.4" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/plugins/scaffolder-backend-module-github/report.api.md b/plugins/scaffolder-backend-module-github/report.api.md index 219506d948..28f710557a 100644 --- a/plugins/scaffolder-backend-module-github/report.api.md +++ b/plugins/scaffolder-backend-module-github/report.api.md @@ -3,13 +3,11 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { AuthService } from '@backstage/backend-plugin-api'; import { BackendFeature } from '@backstage/backend-plugin-api'; -import { CatalogApi } from '@backstage/catalog-client'; +import { CatalogService } from '@backstage/plugin-catalog-node'; import { Config } from '@backstage/config'; import { createPullRequest } from 'octokit-plugin-create-pull-request'; import { GithubCredentialsProvider } from '@backstage/integration'; -import { JsonObject } from '@backstage/types'; import { Octokit } from 'octokit'; import { OctokitOptions } from '@octokit/core/dist-types/types'; import { ScmIntegrationRegistry } from '@backstage/integration'; @@ -25,13 +23,13 @@ export function createGithubActionsDispatchAction(options: { repoUrl: string; workflowId: string; branchOrTagName: string; - workflowInputs?: { - [key: string]: string; - }; - token?: string; + workflowInputs?: Record | undefined; + token?: string | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public @@ -43,11 +41,13 @@ export function createGithubAutolinksAction(options: { repoUrl: string; keyPrefix: string; urlTemplate: string; - isAlphanumeric?: boolean; - token?: string; + isAlphanumeric?: boolean | undefined; + token?: string | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public @@ -56,35 +56,37 @@ export function createGithubBranchProtectionAction(options: { }): TemplateAction< { repoUrl: string; - branch?: string; - enforceAdmins?: boolean; - requiredApprovingReviewCount?: number; - requireCodeOwnerReviews?: boolean; - dismissStaleReviews?: boolean; + branch?: string | undefined; + enforceAdmins?: boolean | undefined; + requiredApprovingReviewCount?: number | undefined; + requireCodeOwnerReviews?: boolean | undefined; + dismissStaleReviews?: boolean | undefined; bypassPullRequestAllowances?: | { - users?: string[]; - teams?: string[]; - apps?: string[]; + apps?: string[] | undefined; + teams?: string[] | undefined; + users?: string[] | undefined; } | undefined; restrictions?: | { - users: string[]; teams: string[]; - apps?: string[]; + users: string[]; + apps?: string[] | undefined; } | undefined; - requiredStatusCheckContexts?: string[]; - requireBranchesToBeUpToDate?: boolean; - requiredConversationResolution?: boolean; - requireLastPushApproval?: boolean; - requiredCommitSigning?: boolean; - requiredLinearHistory?: boolean; - token?: string; + requiredStatusCheckContexts?: string[] | undefined; + requireBranchesToBeUpToDate?: boolean | undefined; + requiredConversationResolution?: boolean | undefined; + requireLastPushApproval?: boolean | undefined; + requiredCommitSigning?: boolean | undefined; + requiredLinearHistory?: boolean | undefined; + token?: string | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public @@ -96,41 +98,42 @@ export function createGithubDeployKeyAction(options: { publicKey: string; privateKey: string; deployKeyName: string; - privateKeySecretName?: string; - token?: string; + privateKeySecretName?: string | undefined; + token?: string | undefined; }, - JsonObject, - 'v1' + { + privateKeySecretName: string; + }, + 'v2' >; // @public export function createGithubEnvironmentAction(options: { integrations: ScmIntegrationRegistry; - catalogClient?: CatalogApi; - auth?: AuthService; + catalog: CatalogService; }): TemplateAction< { repoUrl: string; name: string; - deploymentBranchPolicy?: { - protected_branches: boolean; - custom_branch_policies: boolean; - }; - customBranchPolicyNames?: string[]; - customTagPolicyNames?: string[]; - environmentVariables?: { - [key: string]: string; - }; - secrets?: { - [key: string]: string; - }; - token?: string; - waitTimer?: number; - preventSelfReview?: boolean; - reviewers?: string[]; + deploymentBranchPolicy?: + | { + protected_branches: boolean; + custom_branch_policies: boolean; + } + | undefined; + customBranchPolicyNames?: string[] | undefined; + customTagPolicyNames?: string[] | undefined; + environmentVariables?: Record | undefined; + secrets?: Record | undefined; + token?: string | undefined; + waitTimer?: number | undefined; + preventSelfReview?: boolean | undefined; + reviewers?: string[] | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public @@ -142,10 +145,12 @@ export function createGithubIssuesLabelAction(options: { repoUrl: string; number: number; labels: string[]; - token?: string; + token?: string | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public @@ -155,13 +160,15 @@ export function createGithubPagesEnableAction(options: { }): TemplateAction< { repoUrl: string; - buildType?: 'legacy' | 'workflow'; - sourceBranch?: string; - sourcePath?: '/' | '/docs'; - token?: string; + buildType?: 'legacy' | 'workflow' | undefined; + sourceBranch?: string | undefined; + sourcePath?: '/' | '/docs' | undefined; + token?: string | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public @@ -198,73 +205,76 @@ export function createGithubRepoCreateAction(options: { }): TemplateAction< { repoUrl: string; - description?: string; - homepage?: string; - access?: string; - deleteBranchOnMerge?: boolean; - gitAuthorName?: string; - gitAuthorEmail?: string; - allowRebaseMerge?: boolean; - allowSquashMerge?: boolean; - squashMergeCommitTitle?: 'PR_TITLE' | 'COMMIT_OR_PR_TITLE'; - squashMergeCommitMessage?: 'PR_BODY' | 'COMMIT_MESSAGES' | 'BLANK'; - allowMergeCommit?: boolean; - allowAutoMerge?: boolean; - allowUpdateBranch?: boolean; - requireCodeOwnerReviews?: boolean; - bypassPullRequestAllowances?: { - users?: string[]; - teams?: string[]; - apps?: string[]; - }; - requiredApprovingReviewCount?: number; - restrictions?: { - users: string[]; - teams: string[]; - apps?: string[]; - }; - requiredStatusCheckContexts?: string[]; - requireBranchesToBeUpToDate?: boolean; - requiredConversationResolution?: boolean; - repoVisibility?: 'private' | 'internal' | 'public'; - collaborators?: Array< + description?: string | undefined; + homepage?: string | undefined; + access?: string | undefined; + requireCodeOwnerReviews?: boolean | undefined; + bypassPullRequestAllowances?: | { - user: string; - access: string; + apps?: string[] | undefined; + teams?: string[] | undefined; + users?: string[] | undefined; } + | undefined; + requiredApprovingReviewCount?: number | undefined; + restrictions?: | { - team: string; - access: string; + teams: string[]; + users: string[]; + apps?: string[] | undefined; } + | undefined; + requiredStatusCheckContexts?: string[] | undefined; + requireBranchesToBeUpToDate?: boolean | undefined; + requiredConversationResolution?: boolean | undefined; + repoVisibility?: 'internal' | 'private' | 'public' | undefined; + deleteBranchOnMerge?: boolean | undefined; + allowMergeCommit?: boolean | undefined; + allowSquashMerge?: boolean | undefined; + squashMergeCommitTitle?: 'PR_TITLE' | 'COMMIT_OR_PR_TITLE' | undefined; + squashMergeCommitMessage?: + | 'PR_BODY' + | 'COMMIT_MESSAGES' + | 'BLANK' + | undefined; + allowRebaseMerge?: boolean | undefined; + allowAutoMerge?: boolean | undefined; + allowUpdateBranch?: boolean | undefined; + collaborators?: + | ( + | { + user: string; + access: string; + } + | { + team: string; + access: string; + } + )[] + | undefined; + hasProjects?: boolean | undefined; + hasWiki?: boolean | undefined; + hasIssues?: boolean | undefined; + token?: string | undefined; + topics?: string[] | undefined; + repoVariables?: Record | undefined; + secrets?: Record | undefined; + oidcCustomization?: | { - username: string; - access: 'pull' | 'push' | 'admin' | 'maintain' | 'triage'; + useDefault: boolean; + includeClaimKeys?: string[] | undefined; } - >; - hasProjects?: boolean; - hasWiki?: boolean; - hasIssues?: boolean; - token?: string; - topics?: string[]; - repoVariables?: { - [key: string]: string; - }; - secrets?: { - [key: string]: string; - }; - oidcCustomization?: { - useDefault: boolean; - includeClaimKeys?: string[]; - }; - requireCommitSigning?: boolean; - requiredLinearHistory?: boolean; - customProperties?: { - [key: string]: string; - }; - subscribe?: boolean; + | undefined; + requiredCommitSigning?: boolean | undefined; + requiredLinearHistory?: boolean | undefined; + customProperties?: Record | undefined; + subscribe?: boolean | undefined; }, - JsonObject, - 'v1' + { + remoteUrl: string; + repoContentsUrl: string; + }, + 'v2' >; // @public @@ -275,41 +285,44 @@ export function createGithubRepoPushAction(options: { }): TemplateAction< { repoUrl: string; - description?: string; - defaultBranch?: string; - protectDefaultBranch?: boolean; - protectEnforceAdmins?: boolean; - gitCommitMessage?: string; - gitAuthorName?: string; - gitAuthorEmail?: string; - requireCodeOwnerReviews?: boolean; - dismissStaleReviews?: boolean; + requireCodeOwnerReviews?: boolean | undefined; + dismissStaleReviews?: boolean | undefined; + requiredStatusCheckContexts?: string[] | undefined; bypassPullRequestAllowances?: | { - users?: string[]; - teams?: string[]; - apps?: string[]; + apps?: string[] | undefined; + teams?: string[] | undefined; + users?: string[] | undefined; } | undefined; - requiredApprovingReviewCount?: number; + requiredApprovingReviewCount?: number | undefined; restrictions?: | { - users: string[]; teams: string[]; - apps?: string[]; + users: string[]; + apps?: string[] | undefined; } | undefined; - requiredStatusCheckContexts?: string[]; - requireBranchesToBeUpToDate?: boolean; - requiredConversationResolution?: boolean; - sourcePath?: string; - token?: string; - requiredCommitSigning?: boolean; - requiredLinearHistory?: boolean; - requireLastPushApproval?: boolean; + requireBranchesToBeUpToDate?: boolean | undefined; + requiredConversationResolution?: boolean | undefined; + requireLastPushApproval?: boolean | undefined; + defaultBranch?: string | undefined; + protectDefaultBranch?: boolean | undefined; + protectEnforceAdmins?: boolean | undefined; + gitCommitMessage?: string | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; + sourcePath?: string | undefined; + token?: string | undefined; + requiredCommitSigning?: boolean | undefined; + requiredLinearHistory?: boolean | undefined; }, - JsonObject, - 'v1' + { + remoteUrl: string; + repoContentsUrl: string; + commitHash: string; + }, + 'v2' >; // @public @@ -321,15 +334,17 @@ export function createGithubWebhookAction(options: { { repoUrl: string; webhookUrl: string; - webhookSecret?: string; - events?: string[]; - active?: boolean; - contentType?: 'form' | 'json'; - insecureSsl?: boolean; - token?: string; + webhookSecret?: string | undefined; + events?: string[] | '*'[] | undefined; + active?: boolean | undefined; + contentType?: 'form' | 'json' | undefined; + insecureSsl?: boolean | undefined; + token?: string | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public @@ -340,84 +355,86 @@ export function createPublishGithubAction(options: { }): TemplateAction< { repoUrl: string; - description?: string; - homepage?: string; - access?: string; - defaultBranch?: string; - protectDefaultBranch?: boolean; - protectEnforceAdmins?: boolean; - deleteBranchOnMerge?: boolean; - gitCommitMessage?: string; - gitAuthorName?: string; - gitAuthorEmail?: string; - allowRebaseMerge?: boolean; - allowSquashMerge?: boolean; - squashMergeCommitTitle?: 'PR_TITLE' | 'COMMIT_OR_PR_TITLE'; - squashMergeCommitMessage?: 'PR_BODY' | 'COMMIT_MESSAGES' | 'BLANK'; - allowMergeCommit?: boolean; - allowAutoMerge?: boolean; - allowUpdateBranch?: boolean; - sourcePath?: string; + description?: string | undefined; + homepage?: string | undefined; + access?: string | undefined; bypassPullRequestAllowances?: | { - users?: string[]; - teams?: string[]; - apps?: string[]; + apps?: string[] | undefined; + teams?: string[] | undefined; + users?: string[] | undefined; } | undefined; - requiredApprovingReviewCount?: number; + requiredApprovingReviewCount?: number | undefined; restrictions?: | { - users: string[]; teams: string[]; - apps?: string[]; + users: string[]; + apps?: string[] | undefined; } | undefined; - requireCodeOwnerReviews?: boolean; - dismissStaleReviews?: boolean; - requiredStatusCheckContexts?: string[]; - requireBranchesToBeUpToDate?: boolean; - requiredConversationResolution?: boolean; - requireLastPushApproval?: boolean; - repoVisibility?: 'private' | 'internal' | 'public'; - collaborators?: Array< - | { - user: string; - access: string; - } - | { - team: string; - access: string; - } - | { - username: string; - access: 'pull' | 'push' | 'admin' | 'maintain' | 'triage'; - } - >; + requireCodeOwnerReviews?: boolean | undefined; + dismissStaleReviews?: boolean | undefined; + requiredStatusCheckContexts?: string[] | undefined; + requireBranchesToBeUpToDate?: boolean | undefined; + requiredConversationResolution?: boolean | undefined; + requireLastPushApproval?: boolean | undefined; + repoVisibility?: 'internal' | 'private' | 'public' | undefined; + defaultBranch?: string | undefined; + protectDefaultBranch?: boolean | undefined; + protectEnforceAdmins?: boolean | undefined; + deleteBranchOnMerge?: boolean | undefined; + gitCommitMessage?: string | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; + allowMergeCommit?: boolean | undefined; + allowSquashMerge?: boolean | undefined; + squashMergeCommitTitle?: 'PR_TITLE' | 'COMMIT_OR_PR_TITLE' | undefined; + squashMergeCommitMessage?: + | 'PR_BODY' + | 'COMMIT_MESSAGES' + | 'BLANK' + | undefined; + allowRebaseMerge?: boolean | undefined; + allowAutoMerge?: boolean | undefined; + allowUpdateBranch?: boolean | undefined; + sourcePath?: string | undefined; + collaborators?: + | ( + | { + user: string; + access: string; + } + | { + team: string; + access: string; + } + )[] + | undefined; hasProjects?: boolean | undefined; hasWiki?: boolean | undefined; hasIssues?: boolean | undefined; - token?: string; - topics?: string[]; - repoVariables?: { - [key: string]: string; - }; - secrets?: { - [key: string]: string; - }; - oidcCustomization?: { - useDefault: boolean; - includeClaimKeys?: string[]; - }; - requiredCommitSigning?: boolean; - requiredLinearHistory?: boolean; - customProperties?: { - [key: string]: string; - }; - subscribe?: boolean; + token?: string | undefined; + topics?: string[] | undefined; + repoVariables?: Record | undefined; + secrets?: Record | undefined; + oidcCustomization?: + | { + useDefault: boolean; + includeClaimKeys?: string[] | undefined; + } + | undefined; + requiredCommitSigning?: boolean | undefined; + requiredLinearHistory?: boolean | undefined; + customProperties?: Record | undefined; + subscribe?: boolean | undefined; }, - JsonObject, - 'v1' + { + remoteUrl: string; + repoContentsUrl: string; + commitHash: string; + }, + 'v2' >; // @public @@ -425,28 +442,32 @@ export const createPublishGithubPullRequestAction: ( options: CreateGithubPullRequestActionOptions, ) => TemplateAction< { - title: string; - branchName: string; - targetBranchName?: string; - description: string; repoUrl: string; - draft?: boolean; - targetPath?: string; - sourcePath?: string; - token?: string; - reviewers?: string[]; - assignees?: string[]; - teamReviewers?: string[]; - commitMessage?: string; - update?: boolean; - forceFork?: boolean; - gitAuthorName?: string; - gitAuthorEmail?: string; - forceEmptyGitAuthor?: boolean; - createWhenEmpty?: boolean; + branchName: string; + title: string; + description: string; + targetBranchName?: string | undefined; + draft?: boolean | undefined; + sourcePath?: string | undefined; + targetPath?: string | undefined; + token?: string | undefined; + reviewers?: string[] | undefined; + assignees?: string[] | undefined; + teamReviewers?: string[] | undefined; + commitMessage?: string | undefined; + update?: boolean | undefined; + forceFork?: boolean | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; + forceEmptyGitAuthor?: boolean | undefined; + createWhenEmpty?: boolean | undefined; }, - JsonObject, - 'v1' + { + targetBranchName: string; + remoteUrl: string; + pullRequestNumber: number; + }, + 'v2' >; // @public diff --git a/plugins/scaffolder-backend-module-github/src/actions/github.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/github.examples.test.ts index 4f3cc9f03e..0d279f09b4 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/github.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/github.examples.test.ts @@ -98,7 +98,7 @@ describe('publish:github', () => { jest.requireActual('./helpers'); const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext({ input: { diff --git a/plugins/scaffolder-backend-module-github/src/actions/github.test.ts b/plugins/scaffolder-backend-module-github/src/actions/github.test.ts index f6a67fb398..2fd377ee04 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/github.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/github.test.ts @@ -101,7 +101,7 @@ describe('publish:github', () => { jest.requireActual('./gitHelpers'); const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext({ input: { @@ -605,6 +605,7 @@ describe('publish:github', () => { const customAuthorIntegrations = ScmIntegrations.fromConfig(customAuthorConfig); + const customAuthorAction = createPublishGithubAction({ integrations: customAuthorIntegrations, config: customAuthorConfig, diff --git a/plugins/scaffolder-backend-module-github/src/actions/github.ts b/plugins/scaffolder-backend-module-github/src/actions/github.ts index c47e789bdd..c3583a6e78 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/github.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/github.ts @@ -47,140 +47,61 @@ export function createPublishGithubAction(options: { }) { const { integrations, config, githubCredentialsProvider } = options; - return createTemplateAction<{ - repoUrl: string; - description?: string; - homepage?: string; - access?: string; - defaultBranch?: string; - protectDefaultBranch?: boolean; - protectEnforceAdmins?: boolean; - deleteBranchOnMerge?: boolean; - gitCommitMessage?: string; - gitAuthorName?: string; - gitAuthorEmail?: string; - allowRebaseMerge?: boolean; - allowSquashMerge?: boolean; - squashMergeCommitTitle?: 'PR_TITLE' | 'COMMIT_OR_PR_TITLE'; - squashMergeCommitMessage?: 'PR_BODY' | 'COMMIT_MESSAGES' | 'BLANK'; - allowMergeCommit?: boolean; - allowAutoMerge?: boolean; - allowUpdateBranch?: boolean; - sourcePath?: string; - bypassPullRequestAllowances?: - | { - users?: string[]; - teams?: string[]; - apps?: string[]; - } - | undefined; - requiredApprovingReviewCount?: number; - restrictions?: - | { - users: string[]; - teams: string[]; - apps?: string[]; - } - | undefined; - requireCodeOwnerReviews?: boolean; - dismissStaleReviews?: boolean; - requiredStatusCheckContexts?: string[]; - requireBranchesToBeUpToDate?: boolean; - requiredConversationResolution?: boolean; - requireLastPushApproval?: boolean; - repoVisibility?: 'private' | 'internal' | 'public'; - collaborators?: Array< - | { - user: string; - access: string; - } - | { - team: string; - access: string; - } - | { - /** @deprecated This field is deprecated in favor of team */ - username: string; - access: 'pull' | 'push' | 'admin' | 'maintain' | 'triage'; - } - >; - hasProjects?: boolean | undefined; - hasWiki?: boolean | undefined; - hasIssues?: boolean | undefined; - token?: string; - topics?: string[]; - repoVariables?: { [key: string]: string }; - secrets?: { [key: string]: string }; - oidcCustomization?: { - useDefault: boolean; - includeClaimKeys?: string[]; - }; - requiredCommitSigning?: boolean; - requiredLinearHistory?: boolean; - customProperties?: { [key: string]: string }; - subscribe?: boolean; - }>({ + return createTemplateAction({ id: 'publish:github', description: 'Initializes a git repository of contents in workspace and publishes it to GitHub.', examples, schema: { input: { - type: 'object', - required: ['repoUrl'], - properties: { - repoUrl: inputProps.repoUrl, - description: inputProps.description, - homepage: inputProps.homepage, - access: inputProps.access, - bypassPullRequestAllowances: inputProps.bypassPullRequestAllowances, - requiredApprovingReviewCount: inputProps.requiredApprovingReviewCount, - restrictions: inputProps.restrictions, - requireCodeOwnerReviews: inputProps.requireCodeOwnerReviews, - dismissStaleReviews: inputProps.dismissStaleReviews, - requiredStatusCheckContexts: inputProps.requiredStatusCheckContexts, - requireBranchesToBeUpToDate: inputProps.requireBranchesToBeUpToDate, - requiredConversationResolution: - inputProps.requiredConversationResolution, - requireLastPushApproval: inputProps.requireLastPushApproval, - repoVisibility: inputProps.repoVisibility, - defaultBranch: inputProps.defaultBranch, - protectDefaultBranch: inputProps.protectDefaultBranch, - protectEnforceAdmins: inputProps.protectEnforceAdmins, - deleteBranchOnMerge: inputProps.deleteBranchOnMerge, - gitCommitMessage: inputProps.gitCommitMessage, - gitAuthorName: inputProps.gitAuthorName, - gitAuthorEmail: inputProps.gitAuthorEmail, - allowMergeCommit: inputProps.allowMergeCommit, - allowSquashMerge: inputProps.allowSquashMerge, - squashMergeCommitTitle: inputProps.squashMergeCommitTitle, - squashMergeCommitMessage: inputProps.squashMergeCommitMessage, - allowRebaseMerge: inputProps.allowRebaseMerge, - allowAutoMerge: inputProps.allowAutoMerge, - allowUpdateBranch: inputProps.allowUpdateBranch, - sourcePath: inputProps.sourcePath, - collaborators: inputProps.collaborators, - hasProjects: inputProps.hasProjects, - hasWiki: inputProps.hasWiki, - hasIssues: inputProps.hasIssues, - token: inputProps.token, - topics: inputProps.topics, - repoVariables: inputProps.repoVariables, - secrets: inputProps.secrets, - oidcCustomization: inputProps.oidcCustomization, - requiredCommitSigning: inputProps.requiredCommitSigning, - requiredLinearHistory: inputProps.requiredLinearHistory, - customProperties: inputProps.customProperties, - subscribe: inputProps.subscribe, - }, + repoUrl: inputProps.repoUrl, + description: inputProps.description, + homepage: inputProps.homepage, + access: inputProps.access, + bypassPullRequestAllowances: inputProps.bypassPullRequestAllowances, + requiredApprovingReviewCount: inputProps.requiredApprovingReviewCount, + restrictions: inputProps.restrictions, + requireCodeOwnerReviews: inputProps.requireCodeOwnerReviews, + dismissStaleReviews: inputProps.dismissStaleReviews, + requiredStatusCheckContexts: inputProps.requiredStatusCheckContexts, + requireBranchesToBeUpToDate: inputProps.requireBranchesToBeUpToDate, + requiredConversationResolution: + inputProps.requiredConversationResolution, + requireLastPushApproval: inputProps.requireLastPushApproval, + repoVisibility: inputProps.repoVisibility, + defaultBranch: inputProps.defaultBranch, + protectDefaultBranch: inputProps.protectDefaultBranch, + protectEnforceAdmins: inputProps.protectEnforceAdmins, + deleteBranchOnMerge: inputProps.deleteBranchOnMerge, + gitCommitMessage: inputProps.gitCommitMessage, + gitAuthorName: inputProps.gitAuthorName, + gitAuthorEmail: inputProps.gitAuthorEmail, + allowMergeCommit: inputProps.allowMergeCommit, + allowSquashMerge: inputProps.allowSquashMerge, + squashMergeCommitTitle: inputProps.squashMergeCommitTitle, + squashMergeCommitMessage: inputProps.squashMergeCommitMessage, + allowRebaseMerge: inputProps.allowRebaseMerge, + allowAutoMerge: inputProps.allowAutoMerge, + allowUpdateBranch: inputProps.allowUpdateBranch, + sourcePath: inputProps.sourcePath, + collaborators: inputProps.collaborators, + hasProjects: inputProps.hasProjects, + hasWiki: inputProps.hasWiki, + hasIssues: inputProps.hasIssues, + token: inputProps.token, + topics: inputProps.topics, + repoVariables: inputProps.repoVariables, + secrets: inputProps.secrets, + oidcCustomization: inputProps.oidcCustomization, + requiredCommitSigning: inputProps.requiredCommitSigning, + requiredLinearHistory: inputProps.requiredLinearHistory, + customProperties: inputProps.customProperties, + subscribe: inputProps.subscribe, }, output: { - type: 'object', - properties: { - remoteUrl: outputProps.remoteUrl, - repoContentsUrl: outputProps.repoContentsUrl, - commitHash: outputProps.commitHash, - }, + remoteUrl: outputProps.remoteUrl, + repoContentsUrl: outputProps.repoContentsUrl, + commitHash: outputProps.commitHash, }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.test.ts index 1e59037c04..5583b745d5 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.test.ts @@ -53,7 +53,7 @@ describe('github:actions:dispatch', () => { const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext({ input: { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.test.ts index 7739259c5d..94a51e5b4e 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.test.ts @@ -50,7 +50,7 @@ describe('github:actions:dispatch', () => { const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext({ input: { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts index 40e48bb902..f35cbcc04c 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts @@ -37,52 +37,41 @@ export function createGithubActionsDispatchAction(options: { }) { const { integrations, githubCredentialsProvider } = options; - return createTemplateAction<{ - repoUrl: string; - workflowId: string; - branchOrTagName: string; - workflowInputs?: { [key: string]: string }; - token?: string; - }>({ + return createTemplateAction({ id: 'github:actions:dispatch', description: 'Dispatches a GitHub Action workflow for a given branch or tag', examples, schema: { input: { - type: 'object', - required: ['repoUrl', 'workflowId', 'branchOrTagName'], - properties: { - repoUrl: { - title: 'Repository Location', + repoUrl: z => + z.string({ description: 'Accepts the format `github.com?repo=reponame&owner=owner` where `reponame` is the new repository name and `owner` is an organization or username', - type: 'string', - }, - workflowId: { - title: 'Workflow ID', + }), + workflowId: z => + z.string({ description: 'The GitHub Action Workflow filename', - type: 'string', - }, - branchOrTagName: { - title: 'Branch or Tag name', + }), + branchOrTagName: z => + z.string({ description: 'The git branch or tag name used to dispatch the workflow', - type: 'string', - }, - workflowInputs: { - title: 'Workflow Inputs', - description: - 'Inputs keys and values to send to GitHub Action configured on the workflow file. The maximum number of properties is 10. ', - type: 'object', - }, - token: { - title: 'Authentication Token', - type: 'string', - description: - 'The `GITHUB_TOKEN` to use for authorization to GitHub', - }, - }, + }), + workflowInputs: z => + z + .record(z.string(), { + description: + 'Inputs keys and values to send to GitHub Action configured on the workflow file. The maximum number of properties is 10.', + }) + .optional(), + token: z => + z + .string({ + description: + 'The `GITHUB_TOKEN` to use for authorization to GitHub', + }) + .optional(), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.examples.test.ts index a686ea7f6d..05d19e5ad6 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.examples.test.ts @@ -53,7 +53,7 @@ describe('github:autolinks:create', () => { const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const input = yaml.parse(examples[0].example).steps[0].input; const mockContext = createMockActionContext({ input, diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.test.ts index c378afa17d..532da41946 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.test.ts @@ -51,7 +51,7 @@ describe('github:autolinks:create', () => { const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const workspacePath = createMockDirectory().resolve('workspace'); it('should pass context logger to Octokit client', async () => { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.ts b/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.ts index 50ed052458..a57386a856 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.ts @@ -37,52 +37,41 @@ export function createGithubAutolinksAction(options: { }) { const { integrations, githubCredentialsProvider } = options; - return createTemplateAction<{ - repoUrl: string; - keyPrefix: string; - urlTemplate: string; - isAlphanumeric?: boolean; - token?: string; - }>({ + return createTemplateAction({ id: 'github:autolinks:create', description: 'Create an autolink reference for a repository', examples, schema: { input: { - type: 'object', - required: ['repoUrl', 'keyPrefix', 'urlTemplate'], - properties: { - repoUrl: { - title: 'Repository Location', + repoUrl: z => + z.string({ description: 'Accepts the format `github.com?repo=reponame&owner=owner` where `reponame` is the new repository name and `owner` is an organization or username', - type: 'string', - }, - keyPrefix: { - title: 'Key Prefix', + }), + keyPrefix: z => + z.string({ description: 'This prefix appended by certain characters will generate a link any time it is found in an issue, pull request, or commit.', - type: 'string', - }, - urlTemplate: { - title: 'URL Template', + }), + urlTemplate: z => + z.string({ description: 'The URL must contain `` for the reference number. `` matches different characters depending on the value of isAlphanumeric.', - type: 'string', - }, - isAlphanumeric: { - title: 'Alphanumeric', - description: - 'Whether this autolink reference matches alphanumeric characters. If `true`, the `` parameter of the `url_template` matches alphanumeric characters `A-Z` (case insensitive), `0-9`, and `-`. If `false`, this autolink reference only matches numeric characters. Default: `true`', - type: 'boolean', - default: true, - }, - token: { - title: 'Authentication Token', - type: 'string', - description: 'The token to use for authorization to GitHub', - }, - }, + }), + isAlphanumeric: z => + z + .boolean({ + description: + 'Whether this autolink reference matches alphanumeric characters. If `true`, the `` parameter of the `url_template` matches alphanumeric characters `A-Z` (case insensitive), `0-9`, and `-`. If `false`, this autolink reference only matches numeric characters. Default: `true`', + }) + .default(true) + .optional(), + token: z => + z + .string({ + description: 'The token to use for authorization to GitHub', + }) + .optional(), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.examples.test.ts index 14ebc9a1cd..31a15f6019 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.examples.test.ts @@ -50,7 +50,7 @@ describe('github:branch-protection:create', () => { }); const integrations = ScmIntegrations.fromConfig(config); - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext({ input: { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.test.ts index 190743c556..d20a02d460 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.test.ts @@ -47,7 +47,7 @@ describe('github:branch-protection:create', () => { }); const integrations = ScmIntegrations.fromConfig(config); - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext({ input: { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.ts b/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.ts index 79eebb4096..f82bfc6950 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.ts @@ -36,64 +36,28 @@ export function createGithubBranchProtectionAction(options: { }) { const { integrations } = options; - return createTemplateAction<{ - repoUrl: string; - branch?: string; - enforceAdmins?: boolean; - requiredApprovingReviewCount?: number; - requireCodeOwnerReviews?: boolean; - dismissStaleReviews?: boolean; - bypassPullRequestAllowances?: - | { - users?: string[]; - teams?: string[]; - apps?: string[]; - } - | undefined; - restrictions?: - | { - users: string[]; - teams: string[]; - apps?: string[]; - } - | undefined; - requiredStatusCheckContexts?: string[]; - requireBranchesToBeUpToDate?: boolean; - requiredConversationResolution?: boolean; - requireLastPushApproval?: boolean; - requiredCommitSigning?: boolean; - requiredLinearHistory?: boolean; - token?: string; - }>({ + return createTemplateAction({ id: 'github:branch-protection:create', description: 'Configures Branch Protection', examples, schema: { input: { - type: 'object', - required: ['repoUrl'], - properties: { - repoUrl: inputProps.repoUrl, - branch: { - title: 'Branch name', - description: `The branch to protect. Defaults to the repository's default branch`, - type: 'string', - }, - enforceAdmins: inputProps.protectEnforceAdmins, - requiredApprovingReviewCount: inputProps.requiredApprovingReviewCount, - requireCodeOwnerReviews: inputProps.requireCodeOwnerReviews, - dismissStaleReviews: inputProps.dismissStaleReviews, - bypassPullRequestAllowances: inputProps.bypassPullRequestAllowances, - restrictions: inputProps.restrictions, - requiredStatusCheckContexts: inputProps.requiredStatusCheckContexts, - requireBranchesToBeUpToDate: inputProps.requireBranchesToBeUpToDate, - requiredConversationResolution: - inputProps.requiredConversationResolution, - requireLastPushApproval: inputProps.requireLastPushApproval, - requiredCommitSigning: inputProps.requiredCommitSigning, - requiredLinearHistory: inputProps.requiredLinearHistory, - token: inputProps.token, - }, + repoUrl: inputProps.repoUrl, + branch: inputProps.branch, + enforceAdmins: inputProps.protectEnforceAdmins, + requiredApprovingReviewCount: inputProps.requiredApprovingReviewCount, + requireCodeOwnerReviews: inputProps.requireCodeOwnerReviews, + dismissStaleReviews: inputProps.dismissStaleReviews, + bypassPullRequestAllowances: inputProps.bypassPullRequestAllowances, + restrictions: inputProps.restrictions, + requiredStatusCheckContexts: inputProps.requiredStatusCheckContexts, + requireBranchesToBeUpToDate: inputProps.requireBranchesToBeUpToDate, + requiredConversationResolution: + inputProps.requiredConversationResolution, + requireLastPushApproval: inputProps.requireLastPushApproval, + requiredCommitSigning: inputProps.requiredCommitSigning, + requiredLinearHistory: inputProps.requiredLinearHistory, + token: inputProps.token, }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.examples.test.ts index 62e79dcd68..21d33a24a7 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.examples.test.ts @@ -52,7 +52,7 @@ describe('Usage examples', () => { }, }); const integrations = ScmIntegrations.fromConfig(config); - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext(); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.test.ts index 794ec2cf65..cf506de4b3 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.test.ts @@ -51,7 +51,7 @@ describe('github:deployKey:create', () => { }); const integrations = ScmIntegrations.fromConfig(config); - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext({ input: { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.ts b/plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.ts index 1cb72dac3e..b33e5d1112 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.ts @@ -36,65 +36,50 @@ export function createGithubDeployKeyAction(options: { const { integrations } = options; // For more information on how to define custom actions, see // https://backstage.io/docs/features/software-templates/writing-custom-actions - return createTemplateAction<{ - repoUrl: string; - publicKey: string; - privateKey: string; - deployKeyName: string; - privateKeySecretName?: string; - token?: string; - }>({ + return createTemplateAction({ id: 'github:deployKey:create', description: 'Creates and stores Deploy Keys', examples, schema: { input: { - type: 'object', - required: ['repoUrl', 'publicKey', 'privateKey', 'deployKeyName'], - properties: { - repoUrl: { - title: 'Repository Location', + repoUrl: z => + z.string({ description: 'Accepts the format `github.com?repo=reponame&owner=owner` where `reponame` is the new repository name and `owner` is an organization or username', - type: 'string', - }, - publicKey: { - title: 'SSH Public Key', + }), + publicKey: z => + z.string({ description: 'Generated from `ssh-keygen`. Begins with `ssh-rsa`, `ecdsa-sha2-nistp256`, `ecdsa-sha2-nistp384`, `ecdsa-sha2-nistp521`, `ssh-ed25519`, `sk-ecdsa-sha2-nistp256@openssh.com`, or `sk-ssh-ed25519@openssh.com`.', - type: 'string', - }, - privateKey: { - title: 'SSH Private Key', + }), + privateKey: z => + z.string({ description: 'SSH Private Key generated from `ssh-keygen`', - type: 'string', - }, - deployKeyName: { - title: 'Deploy Key Name', + }), + deployKeyName: z => + z.string({ description: `Name of the Deploy Key`, - type: 'string', - }, - privateKeySecretName: { - title: 'Private Key GitHub Secret Name', - description: - 'Name of the GitHub Secret to store the private key related to the Deploy Key. Defaults to: `KEY_NAME_PRIVATE_KEY` where `KEY_NAME` is the name of the Deploy Key', - type: 'string', - }, - token: { - title: 'Authentication Token', - type: 'string', - description: 'The token to use for authorization to GitHub', - }, - }, + }), + privateKeySecretName: z => + z + .string({ + description: + 'Name of the GitHub Secret to store the private key related to the Deploy Key. Defaults to: `KEY_NAME_PRIVATE_KEY` where `KEY_NAME` is the name of the Deploy Key', + }) + .optional(), + token: z => + z + .string({ + description: 'The token to use for authorization to GitHub', + }) + .optional(), }, output: { - type: 'object', - properties: { - privateKeySecretName: { - title: 'The GitHub Action Repo Secret Name for the Private Key', - type: 'string', - }, - }, + privateKeySecretName: z => + z.string({ + description: + 'The GitHub Action Repo Secret Name for the Private Key', + }), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.examples.test.ts index 9aefd39bb5..c1ddbb5b0e 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.examples.test.ts @@ -16,11 +16,11 @@ import { createGithubEnvironmentAction } from './githubEnvironment'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; +import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import yaml from 'yaml'; import { examples } from './gitHubEnvironment.examples'; -import { CatalogApi } from '@backstage/catalog-client'; const mockOctokit = { rest: { @@ -42,9 +42,7 @@ const mockOctokit = { }, }, }; -const mockCatalogClient: Partial = { - getEntitiesByRefs: jest.fn(), -}; + jest.mock('octokit', () => ({ Octokit: class { constructor() { @@ -52,9 +50,6 @@ jest.mock('octokit', () => ({ } }, })); -jest.mock('@backstage/catalog-client', () => ({ - CatalogClient: mockCatalogClient, -})); const publicKey = '2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvvcCU='; @@ -69,9 +64,10 @@ describe('github:environment:create examples', () => { }); const integrations = ScmIntegrations.fromConfig(config); - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext(); + const mockCatalogService = catalogServiceMock.mock(); beforeEach(() => { mockOctokit.rest.actions.getEnvironmentPublicKey.mockResolvedValue({ @@ -95,15 +91,17 @@ describe('github:environment:create examples', () => { id: 2, }, }); - (mockCatalogClient.getEntitiesByRefs as jest.Mock).mockResolvedValue({ + mockCatalogService.getEntitiesByRefs.mockResolvedValue({ items: [ { + apiVersion: 'v1', kind: 'User', metadata: { name: 'johndoe', }, }, { + apiVersion: 'v1', kind: 'Group', metadata: { name: 'team-a', @@ -114,7 +112,7 @@ describe('github:environment:create examples', () => { action = createGithubEnvironmentAction({ integrations, - catalogClient: mockCatalogClient as CatalogApi, + catalog: mockCatalogService, }); }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.test.ts index 6a75bde69f..fc7c3a8276 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.test.ts @@ -19,10 +19,10 @@ import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test- import { TemplateAction } from '@backstage/plugin-scaffolder-node'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; -import { CatalogApi } from '@backstage/catalog-client'; -import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; +import { mockCredentials } from '@backstage/backend-test-utils'; import { Octokit } from 'octokit'; +import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; const octokitMock = Octokit as unknown as jest.Mock; @@ -47,18 +47,10 @@ const mockOctokit = { }, }; -const mockCatalogClient: Partial = { - getEntitiesByRefs: jest.fn(), -}; - jest.mock('octokit', () => ({ Octokit: jest.fn(), })); -jest.mock('@backstage/catalog-client', () => ({ - CatalogClient: mockCatalogClient, -})); - const publicKey = '2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvvcCU='; describe('github:environment:create', () => { @@ -72,22 +64,17 @@ describe('github:environment:create', () => { }); const integrations = ScmIntegrations.fromConfig(config); + const mockCatalogService = catalogServiceMock.mock(); const credentials = mockCredentials.user(); - const token = mockCredentials.service.token({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); - - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext({ input: { repoUrl: 'github.com?repo=repository&owner=owner', name: 'envname', }, - secrets: { backstageToken: token }, }); beforeEach(() => { @@ -114,15 +101,18 @@ describe('github:environment:create', () => { id: 2, }, }); - (mockCatalogClient.getEntitiesByRefs as jest.Mock).mockResolvedValue({ + + mockCatalogService.getEntitiesByRefs.mockResolvedValue({ items: [ { + apiVersion: '1', kind: 'User', metadata: { name: 'johndoe', }, }, { + apiVersion: '1', kind: 'Group', metadata: { name: 'team-a', @@ -133,8 +123,7 @@ describe('github:environment:create', () => { action = createGithubEnvironmentAction({ integrations, - catalogClient: mockCatalogClient as CatalogApi, - auth: mockServices.auth(), + catalog: mockCatalogService, }); }); @@ -496,11 +485,11 @@ describe('github:environment:create', () => { }, }); - expect(mockCatalogClient.getEntitiesByRefs).toHaveBeenCalledWith( + expect(mockCatalogService.getEntitiesByRefs).toHaveBeenCalledWith( { entityRefs: ['group:default/team-a', 'user:default/johndoe'], }, - { token }, + { credentials }, ); expect( diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts index 522b6ed8b5..4a36be3b5b 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts @@ -24,9 +24,8 @@ import { getOctokitOptions } from '../util'; import { Octokit } from 'octokit'; import Sodium from 'libsodium-wrappers'; import { examples } from './gitHubEnvironment.examples'; -import { CatalogApi } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; -import { AuthService } from '@backstage/backend-plugin-api'; +import { CatalogService } from '@backstage/plugin-catalog-node'; /** * Creates an `github:environment:create` Scaffolder action that creates a Github Environment. @@ -35,124 +34,100 @@ import { AuthService } from '@backstage/backend-plugin-api'; */ export function createGithubEnvironmentAction(options: { integrations: ScmIntegrationRegistry; - catalogClient?: CatalogApi; - auth?: AuthService; + catalog: CatalogService; }) { - const { integrations, catalogClient, auth } = options; + const { integrations, catalog } = options; // For more information on how to define custom actions, see // https://backstage.io/docs/features/software-templates/writing-custom-actions - return createTemplateAction<{ - repoUrl: string; - name: string; - deploymentBranchPolicy?: { - protected_branches: boolean; - custom_branch_policies: boolean; - }; - customBranchPolicyNames?: string[]; - customTagPolicyNames?: string[]; - environmentVariables?: { [key: string]: string }; - secrets?: { [key: string]: string }; - token?: string; - waitTimer?: number; - preventSelfReview?: boolean; - reviewers?: string[]; - }>({ + return createTemplateAction({ id: 'github:environment:create', description: 'Creates Deployment Environments', examples, schema: { input: { - type: 'object', - required: ['repoUrl', 'name'], - properties: { - repoUrl: { - title: 'Repository Location', + repoUrl: z => + z.string({ description: 'Accepts the format `github.com?repo=reponame&owner=owner` where `reponame` is the new repository name and `owner` is an organization or username', - type: 'string', - }, - name: { - title: 'Environment Name', + }), + name: z => + z.string({ description: `Name of the deployment environment to create`, - type: 'string', - }, - deploymentBranchPolicy: { - title: 'Deployment Branch Policy', - description: - 'The type of deployment branch policy for this environment. To allow all branches to deploy, set to `null`.', - type: 'object', - required: ['protected_branches', 'custom_branch_policies'], - properties: { - protected_branches: { - title: 'Protected Branches', - description: - 'Whether only branches with branch protection rules can deploy to this environment. If `protected_branches` is `true`, `custom_branch_policies` must be `false`; if `protected_branches` is `false`, `custom_branch_policies` must be `true`.', - type: 'boolean', + }), + deploymentBranchPolicy: z => + z + .object( + { + protected_branches: z.boolean({ + description: + 'Whether only branches with branch protection rules can deploy to this environment. If `protected_branches` is `true`, `custom_branch_policies` must be `false`; if `protected_branches` is `false`, `custom_branch_policies` must be `true`.', + }), + custom_branch_policies: z.boolean({ + description: + 'Whether only branches that match the specified name patterns can deploy to this environment. If `custom_branch_policies` is `true`, `protected_branches` must be `false`; if `custom_branch_policies` is `false`, `protected_branches` must be `true`.', + }), }, - custom_branch_policies: { - title: 'Custom Branch Policies', + { description: - 'Whether only branches that match the specified name patterns can deploy to this environment. If `custom_branch_policies` is `true`, `protected_branches` must be `false`; if `custom_branch_policies` is `false`, `protected_branches` must be `true`.', - type: 'boolean', + 'The type of deployment branch policy for this environment. To allow all branches to deploy, set to `null`.', }, - }, - }, - customBranchPolicyNames: { - title: 'Custom Branch Policy Name', - description: `The name pattern that branches must match in order to deploy to the environment. + ) + .optional(), + customBranchPolicyNames: z => + z + .array(z.string(), { + description: `The name pattern that branches must match in order to deploy to the environment. Wildcard characters will not match \`/\`. For example, to match branches that begin with \`release/\` and contain an additional single slash, use \`release/*/*\`. For more information about pattern matching syntax, see the Ruby File.fnmatch documentation.`, - type: 'array', - items: { - type: 'string', - }, - }, - customTagPolicyNames: { - title: 'Custom Tag Policy Name', - description: `The name pattern that tags must match in order to deploy to the environment. + }) + .optional(), + customTagPolicyNames: z => + z + .array(z.string(), { + description: `The name pattern that tags must match in order to deploy to the environment. Wildcard characters will not match \`/\`. For example, to match tags that begin with \`release/\` and contain an additional single slash, use \`release/*/*\`. For more information about pattern matching syntax, see the Ruby File.fnmatch documentation.`, - type: 'array', - items: { - type: 'string', - }, - }, - environmentVariables: { - title: 'Environment Variables', - description: `Environment variables attached to the deployment environment`, - type: 'object', - }, - secrets: { - title: 'Deployment Secrets', - description: `Secrets attached to the deployment environment`, - type: 'object', - }, - token: { - title: 'Authentication Token', - type: 'string', - description: 'The token to use for authorization to GitHub', - }, - waitTimer: { - title: 'Wait Timer', - type: 'integer', - description: - 'The time to wait before creating or updating the environment (in milliseconds)', - }, - preventSelfReview: { - title: 'Prevent Self Review', - type: 'boolean', - description: 'Whether to prevent self-review for this environment', - }, - reviewers: { - title: 'Reviewers', - type: 'array', - description: - 'Reviewers for this environment. Must be a list of Backstage entity references.', - items: { - type: 'string', - }, - }, - }, + }) + .optional(), + environmentVariables: z => + z + .record(z.string(), { + description: `Environment variables attached to the deployment environment`, + }) + .optional(), + secrets: z => + z + .record(z.string(), { + description: `Secrets attached to the deployment environment`, + }) + .optional(), + token: z => + z + .string({ + description: 'The token to use for authorization to GitHub', + }) + .optional(), + waitTimer: z => + z + .number({ + description: + 'The time to wait before creating or updating the environment (in milliseconds)', + }) + .optional(), + preventSelfReview: z => + z + .boolean({ + description: + 'Whether to prevent self-review for this environment', + }) + .optional(), + reviewers: z => + z + .array(z.string(), { + description: + 'Reviewers for this environment. Must be a list of Backstage entity references.', + }) + .optional(), }, }, async handler(ctx) { @@ -170,11 +145,6 @@ Wildcard characters will not match \`/\`. For example, to match tags that begin reviewers, } = ctx.input; - const { token } = (await auth?.getPluginRequestToken({ - onBehalfOf: await ctx.getInitiatorCredentials(), - targetPluginId: 'catalog', - })) ?? { token: ctx.secrets?.backstageToken }; - // When environment creation step is executed right after a repo publish step, the repository might not be available immediately. // Add a 2-second delay before initiating the steps in this action. await new Promise(resolve => setTimeout(resolve, 2000)); @@ -213,12 +183,12 @@ Wildcard characters will not match \`/\`. For example, to match tags that begin if (reviewers) { let reviewersEntityRefs: Array = []; // Fetch reviewers from Catalog - const catalogResponse = await catalogClient?.getEntitiesByRefs( + const catalogResponse = await catalog.getEntitiesByRefs( { entityRefs: reviewers, }, { - token, + credentials: await ctx.getInitiatorCredentials(), }, ); if (catalogResponse?.items?.length) { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.examples.test.ts index 4aee57130c..c8a778a069 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.examples.test.ts @@ -61,7 +61,7 @@ describe('github:issues:label examples', () => { const getOctokitOptionsMock = getOctokitOptions as jest.Mock; const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext(); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.test.ts index b1e1c6e87c..d7da860227 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.test.ts @@ -58,7 +58,7 @@ describe('github:issues:label', () => { const getOctokitOptionsMock = getOctokitOptions as jest.Mock; const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext({ input: { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.ts b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.ts index f91bd01a73..bbda5a1839 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.ts @@ -37,46 +37,32 @@ export function createGithubIssuesLabelAction(options: { }) { const { integrations, githubCredentialsProvider } = options; - return createTemplateAction<{ - repoUrl: string; - number: number; - labels: string[]; - token?: string; - }>({ + return createTemplateAction({ id: 'github:issues:label', description: 'Adds labels to a pull request or issue on GitHub.', examples, schema: { input: { - type: 'object', - required: ['repoUrl', 'number', 'labels'], - properties: { - repoUrl: { - title: 'Repository Location', + repoUrl: z => + z.string({ description: 'Accepts the format `github.com?repo=reponame&owner=owner` where `reponame` is the repository name and `owner` is an organization or username', - type: 'string', - }, - number: { - title: 'Pull Request or issue number', + }), + number: z => + z.number({ description: 'The pull request or issue number to add labels to', - type: 'number', - }, - labels: { - title: 'Labels', + }), + labels: z => + z.array(z.string(), { description: 'The labels to add to the pull request or issue', - type: 'array', - items: { - type: 'string', - }, - }, - token: { - title: 'Authentication Token', - type: 'string', - description: - 'The `GITHUB_TOKEN` to use for authorization to GitHub', - }, - }, + }), + token: z => + z + .string({ + description: + 'The `GITHUB_TOKEN` to use for authorization to GitHub', + }) + .optional(), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.examples.test.ts index f0aa0ea5a2..38f7bd8de0 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.examples.test.ts @@ -50,7 +50,7 @@ describe('github:pages', () => { const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext({ input: { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.test.ts index 23e3077861..e81dfa14fd 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.test.ts @@ -48,7 +48,7 @@ describe('github:pages', () => { const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext({ input: { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.ts index 01830a252d..21cde027ec 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.ts @@ -38,55 +38,46 @@ export function createGithubPagesEnableAction(options: { }) { const { integrations, githubCredentialsProvider } = options; - return createTemplateAction<{ - repoUrl: string; - buildType?: 'legacy' | 'workflow'; - sourceBranch?: string; - sourcePath?: '/' | '/docs'; - token?: string; - }>({ + return createTemplateAction({ id: 'github:pages:enable', examples, description: 'Enables GitHub Pages for a repository.', schema: { input: { - type: 'object', - required: ['repoUrl'], - properties: { - repoUrl: { - title: 'Repository Location', + repoUrl: z => + z.string({ description: 'Accepts the format `github.com?repo=reponame&owner=owner` where `reponame` is the new repository name and `owner` is an organization or username', - type: 'string', - }, - buildType: { - title: 'Build Type', - type: 'string', - default: 'workflow', - description: - 'The GitHub Pages build type - `legacy` or `workflow`. Default is `workflow`', - enum: ['legacy', 'workflow'], - }, - sourceBranch: { - title: 'Source Branch', - type: 'string', - default: 'main', - description: 'The GitHub Pages source branch. Default is "main"', - }, - sourcePath: { - title: 'Source Path', - type: 'string', - default: '/', - description: - 'The GitHub Pages source path - "/" or "/docs". Default is "/"', - enum: ['/', '/docs'], - }, - token: { - title: 'Authorization Token', - type: 'string', - description: 'The token to use for authorization to GitHub', - }, - }, + }), + buildType: z => + z + .enum(['legacy', 'workflow'], { + description: + 'The GitHub Pages build type - `legacy` or `workflow`. Default is `workflow`', + }) + .default('workflow') + .optional(), + sourceBranch: z => + z + .string({ + description: 'The GitHub Pages source branch. Default is "main"', + }) + .default('main') + .optional(), + sourcePath: z => + z + .enum(['/', '/docs'], { + description: + 'The GitHub Pages source path - "/" or "/docs". Default is "/"', + }) + .default('/') + .optional(), + token: z => + z + .string({ + description: 'The token to use for authorization to GitHub', + }) + .optional(), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.examples.test.ts index bbbbed6511..27547aa80d 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.examples.test.ts @@ -57,7 +57,7 @@ describe('publish:github:pull-request examples', () => { }); const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext(); let fakeClient: { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts index 44e4cb17b3..670af15595 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts @@ -30,12 +30,12 @@ import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test- type GithubPullRequestActionInput = ReturnType< typeof createPublishGithubPullRequestAction -> extends TemplateAction +> extends TemplateAction ? U : never; describe('createPublishGithubPullRequestAction', () => { - let instance: TemplateAction; + let instance: TemplateAction; let fakeClient: { createPullRequest: jest.Mock; rest: { @@ -97,7 +97,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with targetBranchName', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { fakeClient = { @@ -181,7 +181,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with no sourcePath', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -250,7 +250,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with sourcePath', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -306,7 +306,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with repoUrl', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -359,7 +359,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with reviewers and teamReviewers', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -406,7 +406,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with no reviewers and teamReviewers', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -431,7 +431,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with assignees', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -475,7 +475,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with broken symlink', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -520,7 +520,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with executable file mode 755', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -579,7 +579,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with executable file mode 775', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -638,7 +638,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with commit message', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -683,7 +683,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with force fork', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -729,7 +729,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with author name and email', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -779,7 +779,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with author name', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -828,7 +828,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with author email', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -877,7 +877,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with author from config file', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -946,7 +946,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with author attributes and config file', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -1017,7 +1017,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with author fallback and no config', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -1112,7 +1112,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with createWhenEmpty equals true', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { input = { @@ -1175,7 +1175,7 @@ describe('createPublishGithubPullRequestAction', () => { describe('with createWhenEmpty equals false', () => { let input: GithubPullRequestActionInput; - let ctx: ActionContext; + let ctx: ActionContext; beforeEach(() => { fakeClient.createPullRequest.mockResolvedValueOnce(null); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts index adf80c0e0a..54b90e3544 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts @@ -128,169 +128,141 @@ export const createPublishGithubPullRequestAction = ( config, } = options; - return createTemplateAction<{ - title: string; - branchName: string; - targetBranchName?: string; - description: string; - repoUrl: string; - draft?: boolean; - targetPath?: string; - sourcePath?: string; - token?: string; - reviewers?: string[]; - assignees?: string[]; - teamReviewers?: string[]; - commitMessage?: string; - update?: boolean; - forceFork?: boolean; - gitAuthorName?: string; - gitAuthorEmail?: string; - forceEmptyGitAuthor?: boolean; - createWhenEmpty?: boolean; - }>({ + return createTemplateAction({ id: 'publish:github:pull-request', examples, supportsDryRun: true, schema: { input: { - required: ['repoUrl', 'title', 'description', 'branchName'], - type: 'object', - properties: { - repoUrl: { - title: 'Repository Location', + repoUrl: z => + z.string({ description: 'Accepts the format `github.com?repo=reponame&owner=owner` where `reponame` is the repository name and `owner` is an organization or username', - type: 'string', - }, - branchName: { - type: 'string', - title: 'Branch Name', + }), + branchName: z => + z.string({ description: 'The name for the branch', - }, - targetBranchName: { - type: 'string', - title: 'Target Branch Name', - description: 'The target branch name of the pull request', - }, - title: { - type: 'string', - title: 'Pull Request Name', + }), + targetBranchName: z => + z + .string({ + description: 'The target branch name of the pull request', + }) + .optional(), + title: z => + z.string({ description: 'The name for the pull request', - }, - description: { - type: 'string', - title: 'Pull Request Description', + }), + description: z => + z.string({ description: 'The description of the pull request', - }, - draft: { - type: 'boolean', - title: 'Create as Draft', - description: 'Create a draft pull request', - }, - sourcePath: { - type: 'string', - title: 'Working Subdirectory', - description: - 'Subdirectory of working directory to copy changes from', - }, - targetPath: { - type: 'string', - title: 'Repository Subdirectory', - description: 'Subdirectory of repository to apply changes to', - }, - token: { - title: 'Authentication Token', - type: 'string', - description: 'The token to use for authorization to GitHub', - }, - reviewers: { - title: 'Pull Request Reviewers', - type: 'array', - items: { - type: 'string', - }, - description: - 'The users that will be added as reviewers to the pull request', - }, - assignees: { - title: 'Pull Request Assignees', - type: 'array', - items: { - type: 'string', - }, - description: - 'The users that will be added as assignees to the pull request', - }, - teamReviewers: { - title: 'Pull Request Team Reviewers', - type: 'array', - items: { - type: 'string', - }, - description: - 'The teams that will be added as reviewers to the pull request', - }, - commitMessage: { - type: 'string', - title: 'Commit Message', - description: 'The commit message for the pull request commit', - }, - update: { - type: 'boolean', - title: 'Update', - description: 'Update pull request if already exists', - }, - forceFork: { - type: 'boolean', - title: 'Force Fork', - description: 'Create pull request from a fork', - }, - gitAuthorName: { - type: 'string', - title: 'Default Author Name', - description: - 'Sets the default author name for the commit. The default value is the authenticated user or `Scaffolder`', - }, - gitAuthorEmail: { - type: 'string', - title: 'Default Author Email', - description: - 'Sets the default author email for the commit. The default value is the authenticated user or `scaffolder@backstage.io`', - }, - forceEmptyGitAuthor: { - type: 'boolean', - title: 'Force Empty Git Author', - description: - 'Forces the author to be empty. This is useful when using a Github App, it permit the commit to be verified on Github', - }, - createWhenEmpty: { - type: 'boolean', - title: 'Create When Empty', - description: - 'Set whether to create pull request when there are no changes to commit. The default value is true. If set to false, remoteUrl is no longer a required output.', - }, - }, + }), + draft: z => + z + .boolean({ + description: 'Create a draft pull request', + }) + .optional(), + sourcePath: z => + z + .string({ + description: + 'Subdirectory of working directory to copy changes from', + }) + .optional(), + targetPath: z => + z + .string({ + description: 'Subdirectory of repository to apply changes to', + }) + .optional(), + token: z => + z + .string({ + description: 'The token to use for authorization to GitHub', + }) + .optional(), + reviewers: z => + z + .array(z.string(), { + description: + 'The users that will be added as reviewers to the pull request', + }) + .optional(), + assignees: z => + z + .array(z.string(), { + description: + 'The users that will be added as assignees to the pull request', + }) + .optional(), + teamReviewers: z => + z + .array(z.string(), { + description: + 'The teams that will be added as reviewers to the pull request', + }) + .optional(), + commitMessage: z => + z + .string({ + description: 'The commit message for the pull request commit', + }) + .optional(), + update: z => + z + .boolean({ + description: 'Update pull request if already exists', + }) + .optional(), + forceFork: z => + z + .boolean({ + description: 'Create pull request from a fork', + }) + .optional(), + gitAuthorName: z => + z + .string({ + description: + 'Sets the default author name for the commit. The default value is the authenticated user or `Scaffolder`', + }) + .optional(), + gitAuthorEmail: z => + z + .string({ + description: + 'Sets the default author email for the commit. The default value is the authenticated user or `scaffolder@backstage.io`', + }) + .optional(), + forceEmptyGitAuthor: z => + z + .boolean({ + description: + 'Forces the author to be empty. This is useful when using a Github App, it permit the commit to be verified on Github', + }) + .optional(), + createWhenEmpty: z => + z + .boolean({ + description: + 'Set whether to create pull request when there are no changes to commit. The default value is true. If set to false, remoteUrl is no longer a required output.', + }) + .optional(), }, output: { - required: [], - type: 'object', - properties: { - targetBranchName: { - title: 'Target branch name of the merge request', - type: 'string', - }, - remoteUrl: { - type: 'string', - title: 'Pull Request URL', + targetBranchName: z => + z.string({ + description: 'Target branch name of the merge request', + }), + remoteUrl: z => + z.string({ description: 'Link to the pull request in Github', - }, - pullRequestNumber: { - type: 'number', - title: 'Pull Request Number', + }), + pullRequestNumber: z => + z.number({ description: 'The pull request number', - }, - }, + }), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.test.ts index 8694011133..da4837adb7 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.test.ts @@ -80,7 +80,7 @@ describe('github:repo:create examples', () => { const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext({ input: { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts index 6f901d1d84..cc8f65a5ba 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts @@ -80,7 +80,7 @@ describe('github:repo:create', () => { const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext({ input: { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts index 442af4057c..a85010797d 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts @@ -41,118 +41,50 @@ export function createGithubRepoCreateAction(options: { }) { const { integrations, githubCredentialsProvider } = options; - return createTemplateAction<{ - repoUrl: string; - description?: string; - homepage?: string; - access?: string; - deleteBranchOnMerge?: boolean; - gitAuthorName?: string; - gitAuthorEmail?: string; - allowRebaseMerge?: boolean; - allowSquashMerge?: boolean; - squashMergeCommitTitle?: 'PR_TITLE' | 'COMMIT_OR_PR_TITLE'; - squashMergeCommitMessage?: 'PR_BODY' | 'COMMIT_MESSAGES' | 'BLANK'; - allowMergeCommit?: boolean; - allowAutoMerge?: boolean; - allowUpdateBranch?: boolean; - requireCodeOwnerReviews?: boolean; - bypassPullRequestAllowances?: { - users?: string[]; - teams?: string[]; - apps?: string[]; - }; - requiredApprovingReviewCount?: number; - restrictions?: { - users: string[]; - teams: string[]; - apps?: string[]; - }; - requiredStatusCheckContexts?: string[]; - requireBranchesToBeUpToDate?: boolean; - requiredConversationResolution?: boolean; - repoVisibility?: 'private' | 'internal' | 'public'; - collaborators?: Array< - | { - user: string; - access: string; - } - | { - team: string; - access: string; - } - | { - /** @deprecated This field is deprecated in favor of team */ - username: string; - access: 'pull' | 'push' | 'admin' | 'maintain' | 'triage'; - } - >; - hasProjects?: boolean; - hasWiki?: boolean; - hasIssues?: boolean; - token?: string; - topics?: string[]; - repoVariables?: { [key: string]: string }; - secrets?: { [key: string]: string }; - oidcCustomization?: { - useDefault: boolean; - includeClaimKeys?: string[]; - }; - requireCommitSigning?: boolean; - requiredLinearHistory?: boolean; - customProperties?: { [key: string]: string }; - subscribe?: boolean; - }>({ + return createTemplateAction({ id: 'github:repo:create', description: 'Creates a GitHub repository.', examples, schema: { input: { - type: 'object', - required: ['repoUrl'], - properties: { - repoUrl: inputProps.repoUrl, - description: inputProps.description, - homepage: inputProps.homepage, - access: inputProps.access, - requireCodeOwnerReviews: inputProps.requireCodeOwnerReviews, - bypassPullRequestAllowances: inputProps.bypassPullRequestAllowances, - requiredApprovingReviewCount: inputProps.requiredApprovingReviewCount, - restrictions: inputProps.restrictions, - requiredStatusCheckContexts: inputProps.requiredStatusCheckContexts, - requireBranchesToBeUpToDate: inputProps.requireBranchesToBeUpToDate, - requiredConversationResolution: - inputProps.requiredConversationResolution, - repoVisibility: inputProps.repoVisibility, - deleteBranchOnMerge: inputProps.deleteBranchOnMerge, - allowMergeCommit: inputProps.allowMergeCommit, - allowSquashMerge: inputProps.allowSquashMerge, - squashMergeCommitTitle: inputProps.squashMergeCommitTitle, - squashMergeCommitMessage: inputProps.squashMergeCommitMessage, - allowRebaseMerge: inputProps.allowRebaseMerge, - allowAutoMerge: inputProps.allowAutoMerge, - allowUpdateBranch: inputProps.allowUpdateBranch, - collaborators: inputProps.collaborators, - hasProjects: inputProps.hasProjects, - hasWiki: inputProps.hasWiki, - hasIssues: inputProps.hasIssues, - token: inputProps.token, - topics: inputProps.topics, - repoVariables: inputProps.repoVariables, - secrets: inputProps.secrets, - oidcCustomization: inputProps.oidcCustomization, - requiredCommitSigning: inputProps.requiredCommitSigning, - requiredLinearHistory: inputProps.requiredLinearHistory, - customProperties: inputProps.customProperties, - subscribe: inputProps.subscribe, - }, + repoUrl: inputProps.repoUrl, + description: inputProps.description, + homepage: inputProps.homepage, + access: inputProps.access, + requireCodeOwnerReviews: inputProps.requireCodeOwnerReviews, + bypassPullRequestAllowances: inputProps.bypassPullRequestAllowances, + requiredApprovingReviewCount: inputProps.requiredApprovingReviewCount, + restrictions: inputProps.restrictions, + requiredStatusCheckContexts: inputProps.requiredStatusCheckContexts, + requireBranchesToBeUpToDate: inputProps.requireBranchesToBeUpToDate, + requiredConversationResolution: + inputProps.requiredConversationResolution, + repoVisibility: inputProps.repoVisibility, + deleteBranchOnMerge: inputProps.deleteBranchOnMerge, + allowMergeCommit: inputProps.allowMergeCommit, + allowSquashMerge: inputProps.allowSquashMerge, + squashMergeCommitTitle: inputProps.squashMergeCommitTitle, + squashMergeCommitMessage: inputProps.squashMergeCommitMessage, + allowRebaseMerge: inputProps.allowRebaseMerge, + allowAutoMerge: inputProps.allowAutoMerge, + allowUpdateBranch: inputProps.allowUpdateBranch, + collaborators: inputProps.collaborators, + hasProjects: inputProps.hasProjects, + hasWiki: inputProps.hasWiki, + hasIssues: inputProps.hasIssues, + token: inputProps.token, + topics: inputProps.topics, + repoVariables: inputProps.repoVariables, + secrets: inputProps.secrets, + oidcCustomization: inputProps.oidcCustomization, + requiredCommitSigning: inputProps.requiredCommitSigning, + requiredLinearHistory: inputProps.requiredLinearHistory, + customProperties: inputProps.customProperties, + subscribe: inputProps.subscribe, }, output: { - type: 'object', - properties: { - remoteUrl: outputProps.remoteUrl, - repoContentsUrl: outputProps.repoContentsUrl, - }, + remoteUrl: outputProps.remoteUrl, + repoContentsUrl: outputProps.repoContentsUrl, }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.examples.test.ts index 6570ce8f4b..a581e751b4 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.examples.test.ts @@ -79,7 +79,7 @@ describe('github:repo:push examples', () => { const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext(); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.test.ts index 7ef6e2d793..405ef32f40 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.test.ts @@ -78,7 +78,7 @@ describe('github:repo:push', () => { const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext({ input: { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.ts index 990f3ff7ca..77aabe6b84 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.ts @@ -44,80 +44,39 @@ export function createGithubRepoPushAction(options: { }) { const { integrations, config, githubCredentialsProvider } = options; - return createTemplateAction<{ - repoUrl: string; - description?: string; - defaultBranch?: string; - protectDefaultBranch?: boolean; - protectEnforceAdmins?: boolean; - gitCommitMessage?: string; - gitAuthorName?: string; - gitAuthorEmail?: string; - requireCodeOwnerReviews?: boolean; - dismissStaleReviews?: boolean; - bypassPullRequestAllowances?: - | { - users?: string[]; - teams?: string[]; - apps?: string[]; - } - | undefined; - requiredApprovingReviewCount?: number; - restrictions?: - | { - users: string[]; - teams: string[]; - apps?: string[]; - } - | undefined; - requiredStatusCheckContexts?: string[]; - requireBranchesToBeUpToDate?: boolean; - requiredConversationResolution?: boolean; - sourcePath?: string; - token?: string; - requiredCommitSigning?: boolean; - requiredLinearHistory?: boolean; - requireLastPushApproval?: boolean; - }>({ + return createTemplateAction({ id: 'github:repo:push', description: 'Initializes a git repository of contents in workspace and publishes it to GitHub.', examples, schema: { input: { - type: 'object', - required: ['repoUrl'], - properties: { - repoUrl: inputProps.repoUrl, - requireCodeOwnerReviews: inputProps.requireCodeOwnerReviews, - dismissStaleReviews: inputProps.dismissStaleReviews, - requiredStatusCheckContexts: inputProps.requiredStatusCheckContexts, - bypassPullRequestAllowances: inputProps.bypassPullRequestAllowances, - requiredApprovingReviewCount: inputProps.requiredApprovingReviewCount, - restrictions: inputProps.restrictions, - requireBranchesToBeUpToDate: inputProps.requireBranchesToBeUpToDate, - requiredConversationResolution: - inputProps.requiredConversationResolution, - requireLastPushApproval: inputProps.requireLastPushApproval, - defaultBranch: inputProps.defaultBranch, - protectDefaultBranch: inputProps.protectDefaultBranch, - protectEnforceAdmins: inputProps.protectEnforceAdmins, - gitCommitMessage: inputProps.gitCommitMessage, - gitAuthorName: inputProps.gitAuthorName, - gitAuthorEmail: inputProps.gitAuthorEmail, - sourcePath: inputProps.sourcePath, - token: inputProps.token, - requiredCommitSigning: inputProps.requiredCommitSigning, - requiredLinearHistory: inputProps.requiredLinearHistory, - }, + repoUrl: inputProps.repoUrl, + requireCodeOwnerReviews: inputProps.requireCodeOwnerReviews, + dismissStaleReviews: inputProps.dismissStaleReviews, + requiredStatusCheckContexts: inputProps.requiredStatusCheckContexts, + bypassPullRequestAllowances: inputProps.bypassPullRequestAllowances, + requiredApprovingReviewCount: inputProps.requiredApprovingReviewCount, + restrictions: inputProps.restrictions, + requireBranchesToBeUpToDate: inputProps.requireBranchesToBeUpToDate, + requiredConversationResolution: + inputProps.requiredConversationResolution, + requireLastPushApproval: inputProps.requireLastPushApproval, + defaultBranch: inputProps.defaultBranch, + protectDefaultBranch: inputProps.protectDefaultBranch, + protectEnforceAdmins: inputProps.protectEnforceAdmins, + gitCommitMessage: inputProps.gitCommitMessage, + gitAuthorName: inputProps.gitAuthorName, + gitAuthorEmail: inputProps.gitAuthorEmail, + sourcePath: inputProps.sourcePath, + token: inputProps.token, + requiredCommitSigning: inputProps.requiredCommitSigning, + requiredLinearHistory: inputProps.requiredLinearHistory, }, output: { - type: 'object', - properties: { - remoteUrl: outputProps.remoteUrl, - repoContentsUrl: outputProps.repoContentsUrl, - commitHash: outputProps.commitHash, - }, + remoteUrl: outputProps.remoteUrl, + repoContentsUrl: outputProps.repoContentsUrl, + commitHash: outputProps.commitHash, }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.examples.test.ts index d58d737f93..b76405364b 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.examples.test.ts @@ -53,7 +53,7 @@ describe('github:webhook examples', () => { const defaultWebhookSecret = 'aafdfdivierernfdk23f'; const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; - let action: TemplateAction; + let action: TemplateAction; const mockContext = createMockActionContext(); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.test.ts index a42d251c49..b005d5aa76 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.test.ts @@ -51,7 +51,7 @@ describe('github:repository:webhook:create', () => { const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; const defaultWebhookSecret = 'aafdfdivierernfdk23f'; - let action: TemplateAction; + let action: TemplateAction; beforeEach(() => { jest.resetAllMocks(); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts index 7345f7dd9f..3faab03f13 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts @@ -42,92 +42,74 @@ export function createGithubWebhookAction(options: { const eventNames = emitterEventNames.filter(event => !event.includes('.')); - return createTemplateAction<{ - repoUrl: string; - webhookUrl: string; - webhookSecret?: string; - events?: string[]; - active?: boolean; - contentType?: 'form' | 'json'; - insecureSsl?: boolean; - token?: string; - }>({ + return createTemplateAction({ id: 'github:webhook', description: 'Creates webhook for a repository on GitHub.', examples, supportsDryRun: true, schema: { input: { - type: 'object', - required: ['repoUrl', 'webhookUrl'], - properties: { - repoUrl: { - title: 'Repository Location', + repoUrl: z => + z.string({ description: 'Accepts the format `github.com?repo=reponame&owner=owner` where `reponame` is the new repository name and `owner` is an organization or username', - type: 'string', - }, - webhookUrl: { - title: 'Webhook URL', + }), + webhookUrl: z => + z.string({ description: 'The URL to which the payloads will be delivered', - type: 'string', - }, - webhookSecret: { - title: 'Webhook Secret', - description: - 'Webhook secret value. The default can be provided internally in action creation', - type: 'string', - }, - events: { - title: 'Triggering Events', - description: - 'Determines what events the hook is triggered for. Default: `[push]`', - type: 'array', - default: ['push'], - oneOf: [ - { - items: { - type: 'string', - enum: eventNames, - }, - }, - { - items: { - type: 'string', - const: '*', - }, - }, - ], - }, - active: { - title: 'Active', - type: 'boolean', - default: true, - description: - 'Determines if notifications are sent when the webhook is triggered. Default: `true`', - }, - contentType: { - title: 'Content Type', - type: 'string', - enum: ['form', 'json'], - default: 'form', - description: - 'The media type used to serialize the payloads. The default is `form`', - }, - insecureSsl: { - title: 'Insecure SSL', - type: 'boolean', - default: false, - description: - 'Determines whether the SSL certificate of the host for url will be verified when delivering payloads. Default `false`', - }, - token: { - title: 'Authentication Token', - type: 'string', - description: - 'The `GITHUB_TOKEN` to use for authorization to GitHub', - }, - }, + }), + webhookSecret: z => + z + .string({ + description: + 'Webhook secret value. The default can be provided internally in action creation', + }) + .optional(), + events: z => + z + .union([ + z.array(z.enum(eventNames as [string, ...string[]]), { + description: + 'Determines what events the hook is triggered for. Default: `[push]`', + }), + z.array(z.literal('*'), { + description: + 'Determines what events the hook is triggered for. Use "*" for all events. Default: `[push]`', + }), + ]) + .default(['push']) + .optional(), + active: z => + z + .boolean({ + description: + 'Determines if notifications are sent when the webhook is triggered. Default: `true`', + }) + .default(true) + .optional(), + contentType: z => + z + .enum(['form', 'json'], { + description: + 'The media type used to serialize the payloads. The default is `form`', + }) + .default('form') + .optional(), + insecureSsl: z => + z + .boolean({ + description: + 'Determines whether the SSL certificate of the host for url will be verified when delivering payloads. Default `false`', + }) + .default(false) + .optional(), + token: z => + z + .string({ + description: + 'The `GITHUB_TOKEN` to use for authorization to GitHub', + }) + .optional(), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts b/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts index cac93ec7c6..7b58de4a5a 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts @@ -13,395 +13,440 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { z as zod } from 'zod'; -const repoUrl = { - title: 'Repository Location', - description: - 'Accepts the format `github.com?repo=reponame&owner=owner` where `reponame` is the new repository name and `owner` is an organization or username', - type: 'string', -}; -const description = { - title: 'Repository Description', - type: 'string', -}; -const homepage = { - title: 'Repository Homepage', - type: 'string', -}; -const access = { - title: 'Repository Access', - description: - 'Sets an admin collaborator on the repository. Can either be a user reference different from `owner` in `repoUrl` or team reference, eg. `org/team-name`', - type: 'string', -}; -const requireCodeOwnerReviews = { - title: 'Require CODEOWNER Reviews?', - description: - 'Require an approved review in PR including files with a designated Code Owner', - type: 'boolean', -}; -const dismissStaleReviews = { - title: 'Dismiss Stale Reviews', - description: - 'New reviewable commits pushed to a matching branch will dismiss pull request review approvals.', - type: 'boolean', -}; -const requiredStatusCheckContexts = { - title: 'Required Status Check Contexts', - description: - 'The list of status checks to require in order to merge into this branch', - type: 'array', - items: { - type: 'string', - }, -}; -const requireBranchesToBeUpToDate = { - title: 'Require Branches To Be Up To Date?', - description: - 'Require branches to be up to date before merging. The default value is `true`', - type: 'boolean', - default: true, -}; -const requiredConversationResolution = { - title: 'Required Conversation Resolution', - description: - 'Requires all conversations on code to be resolved before a pull request can be merged into this branch', - type: 'boolean', -}; -const requireLastPushApproval = { - title: 'Require last push approval', - type: 'boolean', - default: false, - description: - 'Whether the most recent push to a PR must be approved by someone other than the person who pushed it. The default value is `false`', -}; -const repoVisibility = { - title: 'Repository Visibility', - type: 'string', - enum: ['private', 'public', 'internal'], -}; -const deleteBranchOnMerge = { - title: 'Delete Branch On Merge', - type: 'boolean', - default: false, - description: - 'Delete the branch after merging the PR. The default value is `false`', -}; -const gitAuthorName = { - title: 'Default Author Name', - type: 'string', - default: 'Scaffolder', - description: - 'Sets the default author name for the commit. The default value is `Scaffolder`', -}; -const gitAuthorEmail = { - title: 'Default Author Email', - type: 'string', - description: `Sets the default author email for the commit.`, -}; -const allowMergeCommit = { - title: 'Allow Merge Commits', - type: 'boolean', - default: true, - description: 'Allow merge commits. The default value is `true`', -}; -const allowSquashMerge = { - title: 'Allow Squash Merges', - type: 'boolean', - default: true, - description: 'Allow squash merges. The default value is `true`', -}; -const allowUpdateBranch = { - title: 'Allow Update Branch', - type: 'boolean', - default: false, - description: 'Allow branch to be updated. The default value is `false`', -}; -const squashMergeCommitTitle = { - title: 'Default squash merge commit title', - enum: ['PR_TITLE', 'COMMIT_OR_PR_TITLE'], - type: 'string', - default: 'COMMIT_OR_PR_TITLE', - description: - 'Sets the default value for a squash merge commit title. The default value is `COMMIT_OR_PR_TITLE`', -}; -const squashMergeCommitMessage = { - title: 'Default squash merge commit message', - enum: ['PR_BODY', 'COMMIT_MESSAGES', 'BLANK'], - type: 'string', - default: 'COMMIT_MESSAGES', - description: - 'Sets the default value for a squash merge commit message. The default value is `COMMIT_MESSAGES`', -}; +const repoUrl = (z: typeof zod) => + z.string({ + description: + 'Accepts the format `github.com?repo=reponame&owner=owner` where `reponame` is the new repository name and `owner` is an organization or username', + }); -const allowRebaseMerge = { - title: 'Allow Rebase Merges', - type: 'boolean', - default: true, - description: 'Allow rebase merges. The default value is `true`', -}; -const allowAutoMerge = { - title: 'Allow Auto Merges', - type: 'boolean', - default: false, - description: - 'Allow individual PRs to merge automatically when all merge requirements are met. The default value is `false`', -}; -const collaborators = { - title: 'Collaborators', - description: 'Provide additional users or teams with permissions', - type: 'array', - items: { - type: 'object', - additionalProperties: false, - required: ['access'], - properties: { - access: { - type: 'string', - description: 'The type of access for the user', +const description = (z: typeof zod) => + z + .string({ + description: 'Repository Description', + }) + .optional(); + +const homepage = (z: typeof zod) => + z + .string({ + description: 'Repository Homepage', + }) + .optional(); + +const access = (z: typeof zod) => + z + .string({ + description: + 'Sets an admin collaborator on the repository. Can either be a user reference different from `owner` in `repoUrl` or team reference, eg. `org/team-name`', + }) + .optional(); + +const requireCodeOwnerReviews = (z: typeof zod) => + z + .boolean({ + description: + 'Require an approved review in PR including files with a designated Code Owner', + }) + .optional(); + +const dismissStaleReviews = (z: typeof zod) => + z + .boolean({ + description: + 'New reviewable commits pushed to a matching branch will dismiss pull request review approvals.', + }) + .optional(); + +const requiredStatusCheckContexts = (z: typeof zod) => + z + .array(z.string(), { + description: + 'The list of status checks to require in order to merge into this branch', + }) + .optional(); + +const requireBranchesToBeUpToDate = (z: typeof zod) => + z + .boolean({ + description: + 'Require branches to be up to date before merging. The default value is `true`', + }) + .default(true) + .optional(); + +const requiredConversationResolution = (z: typeof zod) => + z + .boolean({ + description: + 'Requires all conversations on code to be resolved before a pull request can be merged into this branch', + }) + .optional(); + +const requireLastPushApproval = (z: typeof zod) => + z + .boolean({ + description: + 'Whether the most recent push to a PR must be approved by someone other than the person who pushed it. The default value is `false`', + }) + .default(false) + .optional(); + +const repoVisibility = (z: typeof zod) => + z + .enum(['private', 'public', 'internal'], { + description: 'Repository Visibility', + }) + .optional(); + +const deleteBranchOnMerge = (z: typeof zod) => + z + .boolean({ + description: + 'Delete the branch after merging the PR. The default value is `false`', + }) + .default(false) + .optional(); + +const gitAuthorName = (z: typeof zod) => + z + .string({ + description: + 'Sets the default author name for the commit. The default value is `Scaffolder`', + }) + .default('Scaffolder') + .optional(); + +const gitAuthorEmail = (z: typeof zod) => + z + .string({ + description: `Sets the default author email for the commit.`, + }) + .optional(); + +const allowMergeCommit = (z: typeof zod) => + z + .boolean({ + description: 'Allow merge commits. The default value is `true`', + }) + .default(true) + .optional(); + +const allowSquashMerge = (z: typeof zod) => + z + .boolean({ + description: 'Allow squash merges. The default value is `true`', + }) + .default(true) + .optional(); + +const allowUpdateBranch = (z: typeof zod) => + z + .boolean({ + description: 'Allow branch to be updated. The default value is `false`', + }) + .default(false) + .optional(); + +const squashMergeCommitTitle = (z: typeof zod) => + z + .enum(['PR_TITLE', 'COMMIT_OR_PR_TITLE'], { + description: + 'Sets the default value for a squash merge commit title. The default value is `COMMIT_OR_PR_TITLE`', + }) + .default('COMMIT_OR_PR_TITLE') + .optional(); + +const squashMergeCommitMessage = (z: typeof zod) => + z + .enum(['PR_BODY', 'COMMIT_MESSAGES', 'BLANK'], { + description: + 'Sets the default value for a squash merge commit message. The default value is `COMMIT_MESSAGES`', + }) + .default('COMMIT_MESSAGES') + .optional(); + +const allowRebaseMerge = (z: typeof zod) => + z + .boolean({ + description: 'Allow rebase merges. The default value is `true`', + }) + .default(true) + .optional(); + +const allowAutoMerge = (z: typeof zod) => + z + .boolean({ + description: + 'Allow individual PRs to merge automatically when all merge requirements are met. The default value is `false`', + }) + .default(false) + .optional(); + +const collaborators = (z: typeof zod) => + z + .array( + z.union([ + z.object({ + access: z.string({ + description: 'The type of access for the user', + }), + user: z.string({ + description: + 'The name of the user that will be added as a collaborator', + }), + }), + z.object({ + access: z.string({ + description: 'The type of access for the team', + }), + team: z.string({ + description: + 'The name of the team that will be added as a collaborator', + }), + }), + ]), + { + description: 'Provide additional users or teams with permissions', }, - user: { - type: 'string', + ) + .optional(); + +const hasProjects = (z: typeof zod) => + z + .boolean({ + description: + 'Enable projects for the repository. The default value is `true` unless the organization has disabled repository projects', + }) + .optional(); + +const hasWiki = (z: typeof zod) => + z + .boolean({ + description: + 'Enable the wiki for the repository. The default value is `true`', + }) + .default(true) + .optional(); + +const hasIssues = (z: typeof zod) => + z + .boolean({ + description: + 'Enable issues for the repository. The default value is `true`', + }) + .default(true) + .optional(); + +const token = (z: typeof zod) => + z + .string({ + description: 'The token to use for authorization to GitHub', + }) + .optional(); + +const topics = (z: typeof zod) => + z + .array(z.string(), { + description: 'Adds topics to the repository', + }) + .optional(); + +const defaultBranch = (z: typeof zod) => + z + .string({ + description: `Sets the default branch on the repository. The default value is 'master'`, + }) + .default('master') + .optional(); + +const protectDefaultBranch = (z: typeof zod) => + z + .boolean({ + description: + 'Protect the default branch after creating the repository. The default value is `true`', + }) + .default(true) + .optional(); + +const protectEnforceAdmins = (z: typeof zod) => + z + .boolean({ + description: + 'Enforce admins to adhere to default branch protection. The default value is `true`', + }) + .default(true) + .optional(); + +const bypassPullRequestAllowances = (z: typeof zod) => + z + .object( + { + apps: z.array(z.string()).optional(), + users: z.array(z.string()).optional(), + teams: z.array(z.string()).optional(), + }, + { description: - 'The name of the user that will be added as a collaborator', + 'Allow specific users, teams, or apps to bypass pull request requirements.', }, - team: { - type: 'string', + ) + .optional(); + +const gitCommitMessage = (z: typeof zod) => + z + .string({ + description: + 'Sets the commit message on the repository. The default value is `initial commit`', + }) + .default('initial commit') + .optional(); + +const sourcePath = (z: typeof zod) => + z + .string({ + description: + 'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.', + }) + .optional(); + +const requiredApprovingReviewCount = (z: typeof zod) => + z + .number({ + description: + 'Specify the number of reviewers required to approve pull requests. Use a number between `1` and `6` or `0` to not require reviewers. Defaults to `1`.', + }) + .optional(); + +const restrictions = (z: typeof zod) => + z + .object( + { + apps: z.array(z.string()).optional(), + users: z.array(z.string()), + teams: z.array(z.string()), + }, + { description: - 'The name of the team that will be added as a collaborator', + 'Restrict who can push to the protected branch. User, app, and team restrictions are only available for organization-owned repositories.', }, - }, - oneOf: [{ required: ['user'] }, { required: ['team'] }], - }, -}; -const hasProjects = { - title: 'Enable projects', - type: 'boolean', - description: - 'Enable projects for the repository. The default value is `true` unless the organization has disabled repository projects', -}; -const hasWiki = { - title: 'Enable the wiki', - type: 'boolean', - default: true, - description: - 'Enable the wiki for the repository. The default value is `true`', -}; -const hasIssues = { - title: 'Enable issues', - type: 'boolean', - default: true, - description: 'Enable issues for the repository. The default value is `true`', -}; -const token = { - title: 'Authentication Token', - type: 'string', - description: 'The token to use for authorization to GitHub', -}; -const topics = { - title: 'Topics', - type: 'array', - items: { - type: 'string', - }, -}; -const defaultBranch = { - title: 'Default Branch', - type: 'string', - default: 'master', - description: - 'Sets the default branch on the repository. The default value is `master`', -}; -const protectDefaultBranch = { - title: 'Protect Default Branch', - type: 'boolean', - default: true, - description: - 'Protect the default branch after creating the repository. The default value is `true`', -}; -const protectEnforceAdmins = { - title: 'Enforce Admins On Protected Branches', - type: 'boolean', - default: true, - description: - 'Enforce admins to adhere to default branch protection. The default value is `true`', -}; + ) + .optional(); -const bypassPullRequestAllowances = { - title: 'Bypass pull request requirements', - description: - 'Allow specific users, teams, or apps to bypass pull request requirements.', - type: 'object', - additionalProperties: false, - properties: { - apps: { - type: 'array', - items: { - type: 'string', +const requiredCommitSigning = (z: typeof zod) => + z + .boolean({ + description: `Require commit signing so that you must sign commits on this branch.`, + }) + .optional(); + +const requiredLinearHistory = (z: typeof zod) => + z + .boolean({ + description: `Prevent merge commits from being pushed to matching branches.`, + }) + .optional(); + +const repoVariables = (z: typeof zod) => + z + .record(z.string(), { + description: 'Variables attached to the repository', + }) + .optional(); + +const secrets = (z: typeof zod) => + z + .record(z.string(), { + description: 'Secrets attached to the repository', + }) + .optional(); + +const oidcCustomization = (z: typeof zod) => + z + .object( + { + useDefault: z + .boolean({ + description: `Whether to use the default template or not. If true, includeClaimKeys must not be set.`, + }) + .default(true), + includeClaimKeys: z + .array(z.string(), { + description: `Array of unique strings. Each claim key can only contain alphanumeric characters and underscores.`, + }) + .optional(), }, - }, - users: { - type: 'array', - items: { - type: 'string', + { + description: `OIDC customization template attached to the repository.`, }, - }, - teams: { - type: 'array', - items: { - type: 'string', - }, - }, - }, -}; + ) + .optional(); -const gitCommitMessage = { - title: 'Git Commit Message', - type: 'string', - default: 'initial commit', - description: - 'Sets the commit message on the repository. The default value is `initial commit`', -}; -const sourcePath = { - title: 'Source Path', - description: - 'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.', - type: 'string', -}; +const customProperties = (z: typeof zod) => + z + .record(z.string(), { + description: + 'Custom properties to be added to the repository (note, this only works for organization repositories)', + }) + .optional(); -const requiredApprovingReviewCount = { - title: 'Required approving review count', - type: 'number', - description: - 'Specify the number of reviewers required to approve pull requests. Use a number between `1` and `6` or `0` to not require reviewers. Defaults to `1`.', -}; +const subscribe = (z: typeof zod) => + z + .boolean({ + description: `Subscribe to the repository. The default value is 'false'`, + }) + .optional(); -const restrictions = { - title: 'Restrict who can push to the protected branch', - description: - 'Restrict who can push to the protected branch. User, app, and team restrictions are only available for organization-owned repositories.', - type: 'object', - additionalProperties: false, - properties: { - apps: { - type: 'array', - items: { - type: 'string', - }, - }, - users: { - type: 'array', - items: { - type: 'string', - }, - }, - teams: { - type: 'array', - items: { - type: 'string', - }, - }, - }, -}; +const branch = (z: typeof zod) => + z + .string({ + description: `The branch to protect. Defaults to the repository's default branch`, + }) + .optional(); -const requiredCommitSigning = { - title: 'Require commit signing', - type: 'boolean', - description: `Require commit signing so that you must sign commits on this branch.`, +export { + repoUrl, + description, + homepage, + access, + requireCodeOwnerReviews, + dismissStaleReviews, + requiredStatusCheckContexts, + requireBranchesToBeUpToDate, + requiredConversationResolution, + requireLastPushApproval, + repoVisibility, + deleteBranchOnMerge, + gitAuthorName, + gitAuthorEmail, + allowMergeCommit, + allowSquashMerge, + allowUpdateBranch, + squashMergeCommitTitle, + squashMergeCommitMessage, + allowRebaseMerge, + allowAutoMerge, + collaborators, + hasProjects, + hasWiki, + hasIssues, + token, + topics, + defaultBranch, + gitCommitMessage, + sourcePath, + repoVariables, + secrets, + oidcCustomization, + customProperties, + subscribe, + requiredApprovingReviewCount, + restrictions, + requiredCommitSigning, + requiredLinearHistory, + protectDefaultBranch, + protectEnforceAdmins, + bypassPullRequestAllowances, + branch, }; - -const requiredLinearHistory = { - title: 'Require linear history', - type: 'boolean', - description: `Prevent merge commits from being pushed to matching branches.`, -}; - -const repoVariables = { - title: 'Repository Variables', - description: `Variables attached to the repository`, - type: 'object', -}; - -const secrets = { - title: 'Repository Secrets', - description: `Secrets attached to the repository`, - type: 'object', -}; - -const oidcCustomization = { - title: 'Repository OIDC customization template', - description: `OIDC customization template attached to the repository.`, - type: 'object', - additionalProperties: false, - properties: { - useDefault: { - title: 'Use Default', - type: 'boolean', - description: `Whether to use the default OIDC template or not.`, - }, - includeClaimKeys: { - title: 'Include claim keys', - type: 'array', - items: { - type: 'string', - }, - description: `Array of unique strings. Each claim key can only contain alphanumeric characters and underscores.`, - }, - }, -}; - -const customProperties = { - title: 'Custom Repository Properties', - description: - 'Custom properties to be added to the repository (note, this only works for organization repositories)', - type: 'object', -}; - -const subscribe = { - title: 'Subscribe to repository', - description: `Subscribe to the repository. The default value is 'false'`, - type: 'boolean', -}; - -export { access }; -export { allowMergeCommit }; -export { allowRebaseMerge }; -export { allowSquashMerge }; -export { squashMergeCommitTitle }; -export { squashMergeCommitMessage }; -export { allowAutoMerge }; -export { allowUpdateBranch }; -export { collaborators }; -export { defaultBranch }; -export { deleteBranchOnMerge }; -export { description }; -export { gitAuthorEmail }; -export { gitAuthorName }; -export { gitCommitMessage }; -export { homepage }; -export { protectDefaultBranch }; -export { protectEnforceAdmins }; -export { bypassPullRequestAllowances }; -export { requiredApprovingReviewCount }; -export { restrictions }; -export { repoUrl }; -export { repoVisibility }; -export { requireCodeOwnerReviews }; -export { dismissStaleReviews }; -export { requiredStatusCheckContexts }; -export { requireBranchesToBeUpToDate }; -export { requiredConversationResolution }; -export { requireLastPushApproval }; -export { hasProjects }; -export { hasIssues }; -export { hasWiki }; -export { sourcePath }; -export { token }; -export { topics }; -export { requiredCommitSigning }; -export { requiredLinearHistory }; -export { repoVariables }; -export { secrets }; -export { oidcCustomization }; -export { customProperties }; -export { subscribe }; diff --git a/plugins/scaffolder-backend-module-github/src/actions/outputProperties.ts b/plugins/scaffolder-backend-module-github/src/actions/outputProperties.ts index c0cdcb1cb2..a1ee53fec5 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/outputProperties.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/outputProperties.ts @@ -14,19 +14,22 @@ * limitations under the License. */ -const remoteUrl = { - title: 'A URL to the repository with the provider', - type: 'string', -}; -const repoContentsUrl = { - title: 'A URL to the root of the repository', - type: 'string', -}; +import { z as zod } from 'zod'; -const commitHash = { - title: 'The git commit hash of the initial commit', - type: 'string', -}; +const remoteUrl = (z: typeof zod) => + z.string({ + description: 'A URL to the repository with the provider', + }); + +const repoContentsUrl = (z: typeof zod) => + z.string({ + description: 'A URL to the root of the repository', + }); + +const commitHash = (z: typeof zod) => + z.string({ + description: 'The git commit hash of the initial commit', + }); export { remoteUrl }; export { repoContentsUrl }; diff --git a/plugins/scaffolder-backend-module-github/src/module.ts b/plugins/scaffolder-backend-module-github/src/module.ts index 02dfc2d7f3..5812a22560 100644 --- a/plugins/scaffolder-backend-module-github/src/module.ts +++ b/plugins/scaffolder-backend-module-github/src/module.ts @@ -39,8 +39,8 @@ import { DefaultGithubCredentialsProvider, ScmIntegrations, } from '@backstage/integration'; -import { CatalogClient } from '@backstage/catalog-client'; import { createHandleAutocompleteRequest } from './autocomplete/autocomplete'; +import { catalogServiceRef } from '@backstage/plugin-catalog-node'; /** * @public @@ -54,17 +54,13 @@ export const githubModule = createBackendModule({ deps: { scaffolder: scaffolderActionsExtensionPoint, config: coreServices.rootConfig, - discovery: coreServices.discovery, - auth: coreServices.auth, + catalog: catalogServiceRef, autocomplete: scaffolderAutocompleteExtensionPoint, }, - async init({ scaffolder, config, discovery, auth, autocomplete }) { + async init({ scaffolder, config, autocomplete, catalog }) { const integrations = ScmIntegrations.fromConfig(config); const githubCredentialsProvider = DefaultGithubCredentialsProvider.fromIntegrations(integrations); - const catalogClient = new CatalogClient({ - discoveryApi: discovery, - }); scaffolder.addActions( createGithubActionsDispatchAction({ @@ -80,8 +76,7 @@ export const githubModule = createBackendModule({ }), createGithubEnvironmentAction({ integrations, - catalogClient, - auth, + catalog, }), createGithubIssuesLabelAction({ integrations, diff --git a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md index 3e953122df..a2fb152ee4 100644 --- a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-scaffolder-backend-module-gitlab +## 0.9.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + +## 0.9.2-next.0 + +### Patch Changes + +- 3d6493a: Support merge request labels in publish:gitlab:merge-request +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + ## 0.9.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gitlab/package.json b/plugins/scaffolder-backend-module-gitlab/package.json index 4bff918080..03965d92cd 100644 --- a/plugins/scaffolder-backend-module-gitlab/package.json +++ b/plugins/scaffolder-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitlab", - "version": "0.9.1", + "version": "0.9.2-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-backend-module-gitlab/report.api.md b/plugins/scaffolder-backend-module-gitlab/report.api.md index cdb6065a3b..07e3ed87b4 100644 --- a/plugins/scaffolder-backend-module-gitlab/report.api.md +++ b/plugins/scaffolder-backend-module-gitlab/report.api.md @@ -5,7 +5,6 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; -import { JsonObject } from '@backstage/types'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; @@ -14,6 +13,7 @@ export const createGitlabGroupEnsureExistsAction: (options: { integrations: ScmIntegrationRegistry; }) => TemplateAction< { + repoUrl: string; path: ( | string | { @@ -21,13 +21,12 @@ export const createGitlabGroupEnsureExistsAction: (options: { slug: string; } )[]; - repoUrl: string; token?: string | undefined; }, { groupId?: number | undefined; }, - 'v1' + 'v2' >; // @public @@ -35,29 +34,29 @@ export const createGitlabIssueAction: (options: { integrations: ScmIntegrationRegistry; }) => TemplateAction< { - title: string; repoUrl: string; projectId: number; - labels?: string | undefined; - description?: string | undefined; - weight?: number | undefined; + title: string; token?: string | undefined; assignees?: number[] | undefined; - createdAt?: string | undefined; confidential?: boolean | undefined; - milestoneId?: number | undefined; - epicId?: number | undefined; + description?: string | undefined; + createdAt?: string | undefined; dueDate?: string | undefined; discussionToResolve?: string | undefined; + epicId?: number | undefined; + labels?: string | undefined; issueType?: IssueType | undefined; mergeRequestToResolveDiscussionsOf?: number | undefined; + milestoneId?: number | undefined; + weight?: number | undefined; }, { issueUrl: string; issueId: number; issueIid: number; }, - 'v1' + 'v2' >; // @public @@ -65,18 +64,18 @@ export const createGitlabProjectAccessTokenAction: (options: { integrations: ScmIntegrationRegistry; }) => TemplateAction< { - repoUrl: string; projectId: string | number; - name?: string | undefined; + repoUrl: string; token?: string | undefined; + name?: string | undefined; + accessLevel?: number | undefined; scopes?: string[] | undefined; expiresAt?: string | undefined; - accessLevel?: number | undefined; }, { access_token: string; }, - 'v1' + 'v2' >; // @public @@ -84,18 +83,18 @@ export const createGitlabProjectDeployTokenAction: (options: { integrations: ScmIntegrationRegistry; }) => TemplateAction< { - name: string; - scopes: string[]; repoUrl: string; projectId: string | number; - username?: string | undefined; + name: string; + scopes: string[]; token?: string | undefined; + username?: string | undefined; }, { - user: string; deploy_token: string; + user: string; }, - 'v1' + 'v2' >; // @public @@ -103,19 +102,21 @@ export const createGitlabProjectVariableAction: (options: { integrations: ScmIntegrationRegistry; }) => TemplateAction< { - key: string; - value: string; repoUrl: string; projectId: string | number; + key: string; + value: string; variableType: string; - raw?: boolean | undefined; token?: string | undefined; - masked?: boolean | undefined; - environmentScope?: string | undefined; variableProtected?: boolean | undefined; + masked?: boolean | undefined; + raw?: boolean | undefined; + environmentScope?: string | undefined; }, - any, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public @@ -126,13 +127,17 @@ export const createGitlabRepoPushAction: (options: { repoUrl: string; branchName: string; commitMessage: string; - sourcePath?: string; - targetPath?: string; - token?: string; - commitAction?: 'create' | 'delete' | 'update'; + sourcePath?: string | undefined; + targetPath?: string | undefined; + token?: string | undefined; + commitAction?: 'update' | 'delete' | 'create' | undefined; }, - JsonObject, - 'v1' + { + projectid: string; + projectPath: string; + commitHash: string; + }, + 'v2' >; // @public @@ -142,49 +147,68 @@ export function createPublishGitlabAction(options: { }): TemplateAction< { repoUrl: string; - defaultBranch?: string; - repoVisibility?: 'private' | 'internal' | 'public'; - sourcePath?: string | boolean; - skipExisting?: boolean; - token?: string; - gitCommitMessage?: string; - gitAuthorName?: string; - gitAuthorEmail?: string; - signCommit?: boolean; - setUserAsOwner?: boolean; - topics?: string[]; - settings?: { - path?: string; - auto_devops_enabled?: boolean; - ci_config_path?: string; - description?: string; - merge_method?: 'merge' | 'rebase_merge' | 'ff'; - squash_option?: 'default_off' | 'default_on' | 'never' | 'always'; - topics?: string[]; - visibility?: 'private' | 'internal' | 'public'; - only_allow_merge_if_all_discussions_are_resolved?: boolean; - only_allow_merge_if_pipeline_succeeds?: boolean; - allow_merge_on_skipped_pipeline?: boolean; - }; - branches?: Array<{ - name: string; - protect?: boolean; - create?: boolean; - ref?: string; - }>; - projectVariables?: Array<{ - key: string; - value: string; - description?: string; - variable_type?: string; - protected?: boolean; - masked?: boolean; - raw?: boolean; - environment_scope?: string; - }>; + repoVisibility?: 'internal' | 'private' | 'public' | undefined; + defaultBranch?: string | undefined; + gitCommitMessage?: string | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; + signCommit?: boolean | undefined; + sourcePath?: string | boolean | undefined; + skipExisting?: boolean | undefined; + token?: string | undefined; + setUserAsOwner?: boolean | undefined; + topics?: string[] | undefined; + settings?: + | { + visibility?: 'internal' | 'private' | 'public' | undefined; + path?: string | undefined; + description?: string | undefined; + merge_method?: 'merge' | 'rebase_merge' | 'ff' | undefined; + topics?: string[] | undefined; + auto_devops_enabled?: boolean | undefined; + only_allow_merge_if_pipeline_succeeds?: boolean | undefined; + allow_merge_on_skipped_pipeline?: boolean | undefined; + only_allow_merge_if_all_discussions_are_resolved?: + | boolean + | undefined; + squash_option?: + | 'always' + | 'never' + | 'default_on' + | 'default_off' + | undefined; + ci_config_path?: string | undefined; + } + | undefined; + branches?: + | { + name: string; + ref?: string | undefined; + create?: boolean | undefined; + protect?: boolean | undefined; + }[] + | undefined; + projectVariables?: + | { + key: string; + value: string; + raw?: boolean | undefined; + description?: string | undefined; + protected?: boolean | undefined; + variable_type?: 'file' | 'env_var' | undefined; + masked?: boolean | undefined; + environment_scope?: string | undefined; + }[] + | undefined; }, - JsonObject, - 'v1' + { + remoteUrl: string; + repoContentsUrl: string; + projectId: number; + commitHash: string; + created: boolean; + }, + 'v2' >; // @public @@ -196,19 +220,25 @@ export const createPublishGitlabMergeRequestAction: (options: { title: string; description: string; branchName: string; - targetBranchName?: string; - sourcePath?: string; - targetPath?: string; - token?: string; - commitAction?: 'create' | 'delete' | 'update' | 'skip' | 'auto'; - projectid?: string; - removeSourceBranch?: boolean; - assignee?: string; - reviewers?: string[]; - assignReviewersFromApprovalRules?: boolean; + targetBranchName?: string | undefined; + sourcePath?: string | undefined; + targetPath?: string | undefined; + token?: string | undefined; + commitAction?: 'auto' | 'update' | 'delete' | 'create' | 'skip' | undefined; + projectid?: string | undefined; + removeSourceBranch?: boolean | undefined; + assignee?: string | undefined; + reviewers?: string[] | undefined; + assignReviewersFromApprovalRules?: boolean | undefined; + labels?: string | string[] | undefined; }, - JsonObject, - 'v1' + { + targetBranchName: string; + projectid: string; + projectPath: string; + mergeRequestUrl: string; + }, + 'v2' >; // @public @@ -216,17 +246,17 @@ export const createTriggerGitlabPipelineAction: (options: { integrations: ScmIntegrationRegistry; }) => TemplateAction< { - branch: string; repoUrl: string; projectId: number; tokenDescription: string; + branch: string; token?: string | undefined; variables?: Record | undefined; }, { pipelineUrl: string; }, - 'v1' + 'v2' >; // @public @@ -237,33 +267,33 @@ export const editGitlabIssueAction: (options: { repoUrl: string; projectId: number; issueIid: number; - title?: string | undefined; - labels?: string | undefined; - description?: string | undefined; - weight?: number | undefined; token?: string | undefined; - assignees?: number[] | undefined; addLabels?: string | undefined; + assignees?: number[] | undefined; confidential?: boolean | undefined; + description?: string | undefined; + discussionLocked?: boolean | undefined; + dueDate?: string | undefined; + epicId?: number | undefined; + issueType?: IssueType | undefined; + labels?: string | undefined; milestoneId?: number | undefined; removeLabels?: string | undefined; stateEvent?: IssueStateEvent | undefined; - discussionLocked?: boolean | undefined; - epicId?: number | undefined; - dueDate?: string | undefined; + title?: string | undefined; updatedAt?: string | undefined; - issueType?: IssueType | undefined; + weight?: number | undefined; }, { - state: string; - title: string; - projectId: number; - updatedAt: string; issueUrl: string; + projectId: number; issueId: number; issueIid: number; + state: string; + title: string; + updatedAt: string; }, - 'v1' + 'v2' >; // @public diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts index 753da2e253..7f3f673260 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts @@ -38,307 +38,201 @@ export function createPublishGitlabAction(options: { }) { const { integrations, config } = options; - return createTemplateAction<{ - repoUrl: string; - defaultBranch?: string; - /** @deprecated in favour of settings.visibility field */ - repoVisibility?: 'private' | 'internal' | 'public'; - sourcePath?: string | boolean; - skipExisting?: boolean; - token?: string; - gitCommitMessage?: string; - gitAuthorName?: string; - gitAuthorEmail?: string; - signCommit?: boolean; - setUserAsOwner?: boolean; - /** @deprecated in favour of settings.topics field */ - topics?: string[]; - settings?: { - path?: string; - auto_devops_enabled?: boolean; - ci_config_path?: string; - description?: string; - merge_method?: 'merge' | 'rebase_merge' | 'ff'; - squash_option?: 'default_off' | 'default_on' | 'never' | 'always'; - topics?: string[]; - visibility?: 'private' | 'internal' | 'public'; - only_allow_merge_if_all_discussions_are_resolved?: boolean; - only_allow_merge_if_pipeline_succeeds?: boolean; - allow_merge_on_skipped_pipeline?: boolean; - }; - branches?: Array<{ - name: string; - protect?: boolean; - create?: boolean; - ref?: string; - }>; - projectVariables?: Array<{ - key: string; - value: string; - description?: string; - variable_type?: string; - protected?: boolean; - masked?: boolean; - raw?: boolean; - environment_scope?: string; - }>; - }>({ + return createTemplateAction({ id: 'publish:gitlab', description: 'Initializes a git repository of the content in the workspace, and publishes it to GitLab.', examples, schema: { input: { - type: 'object', - required: ['repoUrl'], - properties: { - repoUrl: { - title: 'Repository Location', - type: 'string', + repoUrl: z => + z.string({ description: `Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where 'project_name' is the repository name and 'group_name' is a group or username`, - }, - repoVisibility: { - title: 'Repository Visibility', - description: `Sets the visibility of the repository. The default value is 'private'. (deprecated, use settings.visibility instead)`, - type: 'string', - enum: ['private', 'public', 'internal'], - }, - defaultBranch: { - title: 'Default Branch', - type: 'string', - description: `Sets the default branch on the repository. The default value is 'master'`, - }, - gitCommitMessage: { - title: 'Git Commit Message', - type: 'string', - description: `Sets the commit message on the repository. The default value is 'initial commit'`, - }, - gitAuthorName: { - title: 'Default Author Name', - type: 'string', - description: `Sets the default author name for the commit. The default value is 'Scaffolder'`, - }, - gitAuthorEmail: { - title: 'Default Author Email', - type: 'string', - description: `Sets the default author email for the commit.`, - }, - signCommit: { - title: 'Sign commit', - type: 'boolean', - description: 'Sign commit with configured PGP private key', - }, - sourcePath: { - title: 'Source Path', - description: - 'Path within the workspace that will be used as the repository root. If omitted or set to true, the entire workspace will be published as the repository. If set to false, the created repository will be empty.', - type: ['string', 'boolean'], - }, - skipExisting: { - title: 'Skip if repository exists', - description: - 'Do not publish the repository if it already exists. The default value is false.', - type: ['boolean'], - }, - token: { - title: 'Authentication Token', - type: 'string', - description: 'The token to use for authorization to GitLab', - }, - setUserAsOwner: { - title: 'Set User As Owner', - type: 'boolean', - description: - 'Set the token user as owner of the newly created repository. Requires a token authorized to do the edit in the integration configuration for the matching host', - }, - topics: { - title: 'Topic labels', - description: - 'Topic labels to apply on the repository. (deprecated, use settings.topics instead)', - type: 'array', - items: { - type: 'string', - }, - }, - settings: { - title: 'Project settings', - description: - 'Additional project settings, based on https://docs.gitlab.com/ee/api/projects.html#create-project attributes', - type: 'object', - properties: { - path: { - title: 'Project path', - description: - 'Repository name for new project. Generated based on name if not provided (generated as lowercase with dashes).', - type: 'string', - }, - auto_devops_enabled: { - title: 'Auto DevOps enabled', - description: 'Enable Auto DevOps for this project', - type: 'boolean', - }, - ci_config_path: { - title: 'CI config path', - description: 'Custom CI config path for this project', - type: 'string', - }, - description: { - title: 'Project description', - description: 'Short project description', - type: 'string', - }, - merge_method: { - title: 'Merge Method to use', - description: 'Merge Methods (merge, rebase_merge, ff)', - type: 'string', - enum: ['merge', 'rebase_merge', 'ff'], - }, - squash_option: { - title: 'Squash option', - description: - 'Set squash option for the project (never, always, default_on, default_off)', - type: 'string', - enum: ['default_off', 'default_on', 'never', 'always'], - }, - topics: { - title: 'Topic labels', - description: 'Topic labels to apply on the repository', - type: 'array', - items: { - type: 'string', - }, - }, - visibility: { - title: 'Project visibility', - description: - 'The visibility of the project. Can be private, internal, or public. The default value is private.', - type: 'string', - enum: ['private', 'public', 'internal'], - }, - only_allow_merge_if_all_discussions_are_resolved: { - title: 'All threads must be resolved', - description: - 'Set whether merge requests can only be merged when all the discussions are resolved.', - type: 'boolean', - }, - only_allow_merge_if_pipeline_succeeds: { - title: 'Pipelines must succeed', - description: - 'Set whether merge requests can only be merged with successful pipelines. This setting is named Pipelines must succeed in the project settings.', - type: 'boolean', - }, - allow_merge_on_skipped_pipeline: { - title: 'Skipped pipelines are considered successful', - description: - 'Set whether or not merge requests can be merged with skipped jobs.', - type: 'boolean', - }, - }, - }, - branches: { - title: 'Project branches settings', - type: 'array', - items: { - type: 'object', - required: ['name'], - properties: { - name: { - title: 'Branch name', - type: 'string', - }, - protect: { - title: 'Should branch be protected', - description: `Will mark branch as protected. The default value is 'false'`, - type: 'boolean', - }, - create: { - title: 'Should branch be created', - description: `If branch does not exist, it will be created from provided ref. The default value is 'false'`, - type: 'boolean', - }, - ref: { - title: 'Branch reference', - description: `Branch reference to create branch from. The default value is 'master'`, - type: 'string', - }, - }, - }, - }, - projectVariables: { - title: 'Project variables', - description: - 'Project variables settings based on Gitlab Project Environments API - https://docs.gitlab.com/ee/api/project_level_variables.html#create-a-variable', - type: 'array', - items: { - type: 'object', - required: ['key', 'value'], - properties: { - key: { - title: 'Variable key', + }), + repoVisibility: z => + z + .enum(['private', 'public', 'internal'], { + description: `Sets the visibility of the repository. The default value is 'private'. (deprecated, use settings.visibility instead)`, + }) + .optional(), + defaultBranch: z => + z + .string({ + description: `Sets the default branch on the repository. The default value is 'master'`, + }) + .optional(), + gitCommitMessage: z => + z + .string({ + description: `Sets the commit message on the repository. The default value is 'initial commit'`, + }) + .optional(), + gitAuthorName: z => + z + .string({ + description: `Sets the default author name for the commit. The default value is 'Scaffolder'`, + }) + .optional(), + gitAuthorEmail: z => + z + .string({ + description: `Sets the default author email for the commit.`, + }) + .optional(), + signCommit: z => + z + .boolean({ + description: 'Sign commit with configured PGP private key', + }) + .optional(), + sourcePath: z => + z + .union([z.string(), z.boolean()], { + description: + 'Path within the workspace that will be used as the repository root. If omitted or set to true, the entire workspace will be published as the repository. If set to false, the created repository will be empty.', + }) + .optional(), + skipExisting: z => + z + .boolean({ + description: + 'Do not publish the repository if it already exists. The default value is false.', + }) + .optional(), + token: z => + z + .string({ + description: 'The token to use for authorization to GitLab', + }) + .optional(), + setUserAsOwner: z => + z + .boolean({ + description: + 'Set the token user as owner of the newly created repository. Requires a token authorized to do the edit in the integration configuration for the matching host', + }) + .optional(), + topics: z => + z + .array(z.string(), { + description: + 'Topic labels to apply on the repository. (deprecated, use settings.topics instead)', + }) + .optional(), + settings: z => + z + .object({ + path: z + .string({ description: - 'The key of a variable; must have no more than 255 characters; only A-Z, a-z, 0-9, and _ are allowed', - type: 'string', - }, - value: { - title: 'Variable value', - description: 'The value of a variable', - type: 'string', - }, - description: { - title: 'Variable description', - description: `The description of the variable. The default value is 'null'`, - type: 'string', - }, - variable_type: { - title: 'Variable type', - description: `The type of a variable. The default value is 'env_var'`, - type: 'string', - enum: ['env_var', 'file'], - }, - protected: { - title: 'Variable protection', - description: `Whether the variable is protected. The default value is 'false'`, - type: 'boolean', - }, - raw: { - title: 'Variable raw', - description: `Whether the variable is in raw format. The default value is 'false'`, - type: 'boolean', - }, - environment_scope: { - title: 'Variable environment scope', - description: `The environment_scope of the variable. The default value is '*'`, - type: 'string', - }, - }, - }, - }, - }, + 'Repository name for new project. Generated based on name if not provided (generated as lowercase with dashes).', + }) + .optional(), + auto_devops_enabled: z + .boolean({ + description: 'Enable Auto DevOps for this project', + }) + .optional(), + ci_config_path: z + .string({ + description: 'Custom CI config path for this project', + }) + .optional(), + description: z + .string({ + description: 'Short project description', + }) + .optional(), + merge_method: z + .enum(['merge', 'rebase_merge', 'ff'], { + description: 'Merge Methods (merge, rebase_merge, ff)', + }) + .optional(), + squash_option: z + .enum(['default_off', 'default_on', 'never', 'always'], { + description: + 'Set squash option for the project (never, always, default_on, default_off)', + }) + .optional(), + topics: z + .array(z.string(), { + description: 'Topic labels to apply on the repository', + }) + .optional(), + visibility: z + .enum(['private', 'public', 'internal'], { + description: + 'The visibility of the project. Can be private, internal, or public. The default value is private.', + }) + .optional(), + only_allow_merge_if_all_discussions_are_resolved: z + .boolean({ + description: + 'Set whether merge requests can only be merged when all the discussions are resolved.', + }) + .optional(), + only_allow_merge_if_pipeline_succeeds: z + .boolean({ + description: + 'Set whether merge requests can only be merged with successful pipelines. This setting is named Pipelines must succeed in the project settings.', + }) + .optional(), + allow_merge_on_skipped_pipeline: z + .boolean({ + description: + 'Set whether or not merge requests can be merged with skipped jobs.', + }) + .optional(), + }) + .optional(), + branches: z => + z + .array( + z.object({ + name: z.string(), + protect: z.boolean().optional(), + create: z.boolean().optional(), + ref: z.string().optional(), + }), + ) + .optional(), + projectVariables: z => + z + .array( + z.object({ + key: z.string(), + value: z.string(), + description: z.string().optional(), + variable_type: z.enum(['env_var', 'file']).optional(), + protected: z.boolean().optional(), + masked: z.boolean().optional(), + raw: z.boolean().optional(), + environment_scope: z.string().optional(), + }), + ) + .optional(), }, output: { - type: 'object', - properties: { - remoteUrl: { - title: 'A URL to the repository with the provider', - type: 'string', - }, - repoContentsUrl: { - title: 'A URL to the root of the repository', - type: 'string', - }, - projectId: { - title: 'The ID of the project', - type: 'number', - }, - commitHash: { - title: 'The git commit hash of the initial commit', - type: 'string', - }, - created: { - title: 'Whether the repository was created or not', - type: 'boolean', - }, - }, + remoteUrl: z => + z.string({ + description: 'A URL to the repository with the provider', + }), + repoContentsUrl: z => + z.string({ + description: 'A URL to the root of the repository', + }), + projectId: z => + z.number({ + description: 'The ID of the project', + }), + commitHash: z => + z.string({ + description: 'The git commit hash of the initial commit', + }), + created: z => + z.boolean({ + description: 'Whether the repository was created or not', + }), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.ts index 7e5b03ce04..c3d4fd74bf 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.ts @@ -17,8 +17,6 @@ import { ScmIntegrationRegistry } from '@backstage/integration'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { GroupSchema } from '@gitbeaker/rest'; -import { z } from 'zod'; -import commonGitlabConfig from '../commonGitlabConfig'; import { getClient, parseRepoUrl } from '../util'; import { examples } from './gitlabGroupEnsureExists.examples'; @@ -38,9 +36,19 @@ export const createGitlabGroupEnsureExistsAction = (options: { supportsDryRun: true, examples, schema: { - input: commonGitlabConfig.merge( - z.object({ - path: z + input: { + repoUrl: z => + z.string({ + description: `Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where 'project_name' is the repository name and 'group_name' is a group or username`, + }), + token: z => + z + .string({ + description: 'The token to use for authorization to GitLab', + }) + .optional(), + path: z => + z .array( z.string().or( z.object({ @@ -54,13 +62,15 @@ export const createGitlabGroupEnsureExistsAction = (options: { }, ) .min(1), - }), - ), - output: z.object({ - groupId: z - .number({ description: 'The id of the innermost sub-group' }) - .optional(), - }), + }, + output: { + groupId: z => + z + .number({ + description: 'The id of the innermost sub-group', + }) + .optional(), + }, }, async handler(ctx) { if (ctx.isDryRun) { diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.ts index b387bf4e8a..1910b5ce6c 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.ts @@ -17,84 +17,12 @@ import { InputError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; -import commonGitlabConfig, { IssueType } from '../commonGitlabConfig'; +import { IssueType } from '../commonGitlabConfig'; import { examples } from './gitlabIssueCreate.examples'; -import { z } from 'zod'; import { checkEpicScope, convertDate, getClient, parseRepoUrl } from '../util'; import { CreateIssueOptions, IssueSchema } from '@gitbeaker/rest'; import { getErrorMessage } from './helpers'; -const issueInputProperties = z.object({ - projectId: z.number().describe('Project Id'), - title: z.string({ description: 'Title of the issue' }), - assignees: z - .array(z.number(), { - description: 'IDs of the users to assign the issue to.', - }) - .optional(), - confidential: z.boolean({ description: 'Issue Confidentiality' }).optional(), - description: z.string().describe('Issue description').max(1048576).optional(), - createdAt: z - .string() - .describe('Creation date/time') - .regex( - /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/, - 'Invalid date format. Use YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss.SSSZ', - ) - .optional(), - dueDate: z - .string() - .describe('Due date/time') - .regex( - /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/, - 'Invalid date format. Use YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss.SSSZ', - ) - .optional(), - discussionToResolve: z - .string({ - description: - 'Id of a discussion to resolve. Use in combination with "merge_request_to_resolve_discussions_of"', - }) - .optional(), - epicId: z - .number({ description: 'Id of the linked Epic' }) - .min(0, 'Valid values should be equal or greater than zero') - .optional(), - labels: z.string({ description: 'Labels to apply' }).optional(), - issueType: z - .nativeEnum(IssueType, { - description: 'Type of the issue', - }) - .optional(), - mergeRequestToResolveDiscussionsOf: z - .number({ - description: 'IID of a merge request in which to resolve all issues', - }) - .optional(), - milestoneId: z - .number({ description: 'Global ID of a milestone to assign the issue' }) - .optional(), - weight: z - .number({ description: 'The issue weight' }) - .min(0) - .refine(value => { - const isValid = value >= 0; - if (!isValid) { - return { - message: 'Valid values should be equal or greater than zero', - }; - } - return isValid; - }) - .optional(), -}); - -const issueOutputProperties = z.object({ - issueUrl: z.string({ description: 'Issue Url' }), - issueId: z.number({ description: 'Issue Id' }), - issueIid: z.number({ description: 'Issue Iid' }), -}); - /** * Creates a `gitlab:issues:create` Scaffolder action. * @@ -110,8 +38,133 @@ export const createGitlabIssueAction = (options: { description: 'Creates a Gitlab issue.', examples, schema: { - input: commonGitlabConfig.merge(issueInputProperties), - output: issueOutputProperties, + input: { + repoUrl: z => + z.string({ + description: `Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where 'project_name' is the repository name and 'group_name' is a group or username`, + }), + token: z => + z + .string({ + description: 'The token to use for authorization to GitLab', + }) + .optional(), + projectId: z => + z.number({ + description: 'Project Id', + }), + title: z => + z.string({ + description: 'Title of the issue', + }), + assignees: z => + z + .array(z.number(), { + description: 'IDs of the users to assign the issue to.', + }) + .optional(), + confidential: z => + z + .boolean({ + description: 'Issue Confidentiality', + }) + .optional(), + description: z => + z + .string({ + description: 'Issue description', + }) + .max(1048576) + .optional(), + createdAt: z => + z + .string({ + description: 'Creation date/time', + }) + .regex( + /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/, + 'Invalid date format. Use YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss.SSSZ', + ) + .optional(), + dueDate: z => + z + .string({ + description: 'Due date/time', + }) + .regex( + /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/, + 'Invalid date format. Use YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss.SSSZ', + ) + .optional(), + discussionToResolve: z => + z + .string({ + description: + 'Id of a discussion to resolve. Use in combination with "merge_request_to_resolve_discussions_of"', + }) + .optional(), + epicId: z => + z + .number({ + description: 'Id of the linked Epic', + }) + .min(0, 'Valid values should be equal or greater than zero') + .optional(), + labels: z => + z + .string({ + description: 'Labels to apply', + }) + .optional(), + issueType: z => + z + .nativeEnum(IssueType, { + description: 'Type of the issue', + }) + .optional(), + mergeRequestToResolveDiscussionsOf: z => + z + .number({ + description: + 'IID of a merge request in which to resolve all issues', + }) + .optional(), + milestoneId: z => + z + .number({ + description: 'Global ID of a milestone to assign the issue', + }) + .optional(), + weight: z => + z + .number({ + description: 'The issue weight', + }) + .min(0) + .refine( + value => { + return value >= 0; + }, + { + message: 'Valid values should be equal or greater than zero', + }, + ) + .optional(), + }, + output: { + issueUrl: z => + z.string({ + description: 'Issue Url', + }), + issueId: z => + z.number({ + description: 'Issue Id', + }), + issueIid: z => + z.number({ + description: 'Issue Iid', + }), + }, }, async handler(ctx) { try { @@ -132,7 +185,7 @@ export const createGitlabIssueAction = (options: { milestoneId, weight, token, - } = commonGitlabConfig.merge(issueInputProperties).parse(ctx.input); + } = ctx.input; const { host } = parseRepoUrl(repoUrl, integrations); const api = getClient({ host, integrations, token }); @@ -201,12 +254,6 @@ export const createGitlabIssueAction = (options: { ctx.output('issueUrl', response.web_url); ctx.output('issueIid', response.iid); } catch (error: any) { - if (error instanceof z.ZodError) { - // Handling Zod validation errors - throw new InputError(`Validation error: ${error.message}`, { - validationErrors: error.errors, - }); - } // Handling other errors throw new InputError( `Failed to create GitLab issue: ${getErrorMessage(error)}`, diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.ts index 0754892308..bb3e717291 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.ts @@ -17,123 +17,12 @@ import { InputError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; -import commonGitlabConfig, { - IssueStateEvent, - IssueType, -} from '../commonGitlabConfig'; +import { IssueStateEvent, IssueType } from '../commonGitlabConfig'; import { examples } from './gitlabIssueEdit.examples'; -import { z } from 'zod'; import { checkEpicScope, convertDate, getClient, parseRepoUrl } from '../util'; import { EditIssueOptions, IssueSchema } from '@gitbeaker/rest'; import { getErrorMessage } from './helpers'; -const editIssueInputProperties = z.object({ - projectId: z - .number() - .describe( - 'The global ID or URL-encoded path of the project owned by the authenticated user.', - ), - issueIid: z.number().describe("The internal ID of a project's issue"), - addLabels: z - .string({ - description: - 'Comma-separated label names to add to an issue. If a label does not already exist, this creates a new project label and assigns it to the issue.', - }) - .optional(), - assignees: z - .array(z.number(), { - description: 'IDs of the users to assign the issue to.', - }) - .optional(), - confidential: z - .boolean({ description: 'Updates an issue to be confidential.' }) - .optional(), - description: z - .string() - .describe('The description of an issue. Limited to 1,048,576 characters.') - .max(1048576) - .optional(), - discussionLocked: z - .boolean({ - description: - 'Flag indicating if the issue’s discussion is locked. If the discussion is locked only project members can add or edit comments.', - }) - .optional(), - dueDate: z - .string() - .describe( - 'The due date. Date time string in the format YYYY-MM-DD, for example 2016-03-11.', - ) - .regex(/^\d{4}-\d{2}-\d{2}$/, 'Invalid date format. Use YYYY-MM-DD') - .optional(), - epicId: z - .number({ - description: - 'ID of the epic to add the issue to. Valid values are greater than or equal to 0.', - }) - .min(0, 'Valid values should be equal or greater than zero') - .optional(), - issueType: z - .nativeEnum(IssueType, { - description: - 'Updates the type of issue. One of issue, incident, test_case or task.', - }) - .optional(), - labels: z - .string({ - description: - 'Comma-separated label names for an issue. Set to an empty string to unassign all labels. If a label does not already exist, this creates a new project label and assigns it to the issue.', - }) - .optional(), - milestoneId: z - .number({ - description: - 'The global ID of a milestone to assign the issue to. Set to 0 or provide an empty value to unassign a milestone', - }) - .optional(), - removeLabels: z - .string({ - description: 'Comma-separated label names to remove from an issue.', - }) - .optional(), - stateEvent: z - .nativeEnum(IssueStateEvent, { - description: - 'The state event of an issue. To close the issue, use close, and to reopen it, use reopen.', - }) - .optional(), - title: z.string().describe('The title of an issue.').optional(), - updatedAt: z - .string() - .describe( - 'When the issue was updated. Date time string, ISO 8601 formatted', - ) - .regex( - /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/, - 'Invalid date format. Use YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss.SSSZ', - ) - .optional(), - weight: z - .number({ description: 'The issue weight' }) - .min(0, 'Valid values should be equal or greater than zero') - .max(10, 'Valid values should be equal or less than 10') - .optional(), -}); - -const editIssueOutputProperties = z.object({ - issueUrl: z.string({ description: 'Issue WebUrl' }), - projectId: z.number({ - description: 'The project id the issue belongs to WebUrl', - }), - issueId: z.number({ description: 'The issues Id' }), - issueIid: z.number({ - description: "The issues internal ID of a project's issue", - }), - state: z.string({ description: 'The state event of an issue' }), - title: z.string({ description: 'The title of an issue.' }), - updatedAt: z.string({ description: 'The last updated time of the issue.' }), -}); - /** * Creates a `gitlab:issue:edit` Scaffolder action. * @@ -149,8 +38,167 @@ export const editGitlabIssueAction = (options: { description: 'Edit a Gitlab issue.', examples, schema: { - input: commonGitlabConfig.merge(editIssueInputProperties), - output: editIssueOutputProperties, + input: { + repoUrl: z => + z.string({ + description: `Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where 'project_name' is the repository name and 'group_name' is a group or username`, + }), + token: z => + z + .string({ + description: 'The token to use for authorization to GitLab', + }) + .optional(), + projectId: z => + z.number({ + description: + 'The global ID or URL-encoded path of the project owned by the authenticated user.', + }), + issueIid: z => + z.number({ + description: "The internal ID of a project's issue", + }), + addLabels: z => + z + .string({ + description: + 'Comma-separated label names to add to an issue. If a label does not already exist, this creates a new project label and assigns it to the issue.', + }) + .optional(), + assignees: z => + z + .array(z.number(), { + description: 'IDs of the users to assign the issue to.', + }) + .optional(), + confidential: z => + z + .boolean({ + description: 'Updates an issue to be confidential.', + }) + .optional(), + description: z => + z + .string({ + description: + 'The description of an issue. Limited to 1,048,576 characters.', + }) + .max(1048576) + .optional(), + discussionLocked: z => + z + .boolean({ + description: + 'Flag indicating if the issue discussion is locked. If the discussion is locked only project members can add or edit comments.', + }) + .optional(), + dueDate: z => + z + .string({ + description: + 'The due date. Date time string in the format YYYY-MM-DD, for example 2016-03-11.', + }) + .regex(/^\d{4}-\d{2}-\d{2}$/, 'Invalid date format. Use YYYY-MM-DD') + .optional(), + epicId: z => + z + .number({ + description: + 'ID of the epic to add the issue to. Valid values are greater than or equal to 0.', + }) + .min(0, 'Valid values should be equal or greater than zero') + .optional(), + issueType: z => + z + .nativeEnum(IssueType, { + description: + 'Updates the type of issue. One of issue, incident, test_case or task.', + }) + .optional(), + labels: z => + z + .string({ + description: + 'Comma-separated label names for an issue. Set to an empty string to unassign all labels. If a label does not already exist, this creates a new project label and assigns it to the issue.', + }) + .optional(), + milestoneId: z => + z + .number({ + description: + 'The global ID of a milestone to assign the issue to. Set to 0 or provide an empty value to unassign a milestone', + }) + .optional(), + removeLabels: z => + z + .string({ + description: + 'Comma-separated label names to remove from an issue.', + }) + .optional(), + stateEvent: z => + z + .nativeEnum(IssueStateEvent, { + description: + 'The state event of an issue. To close the issue, use close, and to reopen it, use reopen.', + }) + .optional(), + title: z => + z + .string({ + description: 'The title of an issue.', + }) + .optional(), + updatedAt: z => + z + .string({ + description: + 'When the issue was updated. Date time string, ISO 8601 formatted', + }) + .regex( + /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/, + 'Invalid date format. Use YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss.SSSZ', + ) + .optional(), + weight: z => + z + .number({ + description: 'The issue weight', + }) + .min(0, 'Valid values should be equal or greater than zero') + .max(10, 'Valid values should be equal or less than 10') + .optional(), + }, + output: { + issueUrl: z => + z.string({ + description: 'Issue WebUrl', + }), + projectId: z => + z.number({ + description: 'The project id the issue belongs to WebUrl', + }), + issueId: z => + z.number({ + description: 'The issues Id', + }), + issueIid: z => + z.number({ + description: "The issues internal ID of a project's issue", + }), + state: z => + z.string({ + description: 'The state event of an issue', + }), + title: z => + z.string({ + description: 'The title of an issue.', + }), + updatedAt: z => + z.string({ + description: 'The last updated time of the issue.', + }), + }, }, async handler(ctx) { try { @@ -174,7 +222,7 @@ export const editGitlabIssueAction = (options: { stateEvent, weight, token, - } = commonGitlabConfig.merge(editIssueInputProperties).parse(ctx.input); + } = ctx.input; const { host } = parseRepoUrl(repoUrl, integrations); const api = getClient({ host, integrations, token }); @@ -252,12 +300,6 @@ export const editGitlabIssueAction = (options: { ctx.output('state', editedIssue.state); ctx.output('updatedAt', editedIssue.updatedAt); } catch (error: any) { - if (error instanceof z.ZodError) { - // Handling Zod validation errors - throw new InputError(`Validation error: ${error.message}`, { - validationErrors: error.errors, - }); - } // Handling other errors throw new InputError( `Failed to edit/modify GitLab issue: ${getErrorMessage(error)}`, diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.examples.test.ts index a97159a6b9..27f39fe621 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.examples.test.ts @@ -125,7 +125,7 @@ jest.mock('@gitbeaker/rest', () => ({ })); describe('createGitLabMergeRequest', () => { - let instance: TemplateAction; + let instance: TemplateAction; const mockDir = createMockDirectory(); const workspacePath = mockDir.resolve('workspace'); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.test.ts index c5161b199d..0cc1ca60c3 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.test.ts @@ -238,7 +238,7 @@ jest.mock('@gitbeaker/rest', () => ({ })); describe('createGitLabMergeRequest', () => { - let instance: TemplateAction; + let instance: TemplateAction; const mockDir = createMockDirectory(); const workspacePath = mockDir.resolve('workspace'); @@ -1446,4 +1446,66 @@ describe('createGitLabMergeRequest', () => { ); }); }); + describe('with labels', () => { + it('handles single label', async () => { + const input = { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + title: 'Create my new MR', + branchName: 'new-mr', + description: 'MR description', + commitAction: 'skip', + labels: 'single-label', + }; + const ctx = createMockActionContext({ input, workspacePath }); + await instance.handler(ctx); + + expect(mockGitlabClient.Branches.create).toHaveBeenCalledWith( + 'owner/repo', + 'new-mr', + 'main', + ); + expect(mockGitlabClient.Commits.create).not.toHaveBeenCalled(); + expect(mockGitlabClient.MergeRequests.create).toHaveBeenCalledWith( + 'owner/repo', + 'new-mr', + 'main', + 'Create my new MR', + { + description: 'MR description', + removeSourceBranch: false, + labels: 'single-label', + }, + ); + }); + it('handles array of labels', async () => { + const input = { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + title: 'Create my new MR', + branchName: 'new-mr', + description: 'MR description', + commitAction: 'skip', + labels: ['foo', 'bar', 'baz'], + }; + const ctx = createMockActionContext({ input, workspacePath }); + await instance.handler(ctx); + + expect(mockGitlabClient.Branches.create).toHaveBeenCalledWith( + 'owner/repo', + 'new-mr', + 'main', + ); + expect(mockGitlabClient.Commits.create).not.toHaveBeenCalled(); + expect(mockGitlabClient.MergeRequests.create).toHaveBeenCalledWith( + 'owner/repo', + 'new-mr', + 'main', + 'Create my new MR', + { + description: 'MR description', + removeSourceBranch: false, + labels: ['foo', 'bar', 'baz'], + }, + ); + }); + }); }); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts index 61a6c04889..a9ee80a30f 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts @@ -134,8 +134,10 @@ async function getReviewersFromApprovalRules( } } +const commitActions = ['create', 'delete', 'update', 'skip', 'auto'] as const; + /** - * Create a new action that creates a gitlab merge request. + * Create a new action that creates a GitLab merge request. * * @public */ @@ -144,138 +146,96 @@ export const createPublishGitlabMergeRequestAction = (options: { }) => { const { integrations } = options; - return createTemplateAction<{ - repoUrl: string; - title: string; - description: string; - branchName: string; - targetBranchName?: string; - sourcePath?: string; - targetPath?: string; - token?: string; - commitAction?: 'create' | 'delete' | 'update' | 'skip' | 'auto'; - /** @deprecated projectID passed as query parameters in the repoUrl */ - projectid?: string; - removeSourceBranch?: boolean; - assignee?: string; - reviewers?: string[]; - assignReviewersFromApprovalRules?: boolean; - }>({ + return createTemplateAction({ id: 'publish:gitlab:merge-request', examples, schema: { input: { - required: ['repoUrl', 'branchName'], - type: 'object', - properties: { - repoUrl: { - type: 'string', - title: 'Repository Location', - description: `\ -Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where \ -'project_name' is the repository name and 'group_name' is a group or username`, - }, - /** @deprecated projectID is passed as query parameters in the repoUrl */ - projectid: { - type: 'string', - title: 'projectid', - description: 'Project ID/Name(slug) of the Gitlab Project', - }, - title: { - type: 'string', - title: 'Merge Request Name', - description: 'The name for the merge request', - }, - description: { - type: 'string', - title: 'Merge Request Description', - description: 'The description of the merge request', - }, - branchName: { - type: 'string', - title: 'Source Branch Name', - description: 'The source branch name of the merge request', - }, - targetBranchName: { - type: 'string', - title: 'Target Branch Name', - description: 'The target branch name of the merge request', - }, - sourcePath: { - type: 'string', - title: 'Working Subdirectory', - description: `\ + repoUrl: z => + z.string().describe(`\ +Accepts the format \`gitlab.com?repo=project_name&owner=group_name\` where \ +\`project_name\` is the repository name and \`group_name\` is a group or username`), + title: z => z.string().describe('The name for the merge request'), + description: z => + z.string().describe('The description of the merge request'), + branchName: z => + z.string().describe('The source branch name of the merge request'), + targetBranchName: z => + z + .string() + .optional() + .describe( + 'The target branch name of the merge request (defaults to _default branch of repository_)', + ), + sourcePath: z => + z.string().optional().describe(`\ Subdirectory of working directory to copy changes from. \ -For reasons of backward compatibility, any specified 'targetPath' input will \ +For reasons of backward compatibility, any specified \`targetPath\` input will \ be applied in place of an absent/falsy value for this input. \ -Circumvent this behavior using '.'`, - }, - targetPath: { - type: 'string', - title: 'Repository Subdirectory', - description: 'Subdirectory of repository to apply changes to', - }, - token: { - title: 'Authentication Token', - type: 'string', - description: 'The token to use for authorization to GitLab', - }, - commitAction: { - title: 'Commit action', - type: 'string', - enum: ['create', 'update', 'delete', 'auto'], - description: `\ -The action to be used for git commit. Defaults to the custom 'auto' action provided by backstage, -which uses additional API calls in order to detect whether to 'create', 'update' or 'skip' each source file.`, - }, - removeSourceBranch: { - title: 'Delete source branch', - type: 'boolean', - description: - 'Option to delete source branch once the MR has been merged. Default: false', - }, - assignee: { - title: 'Merge Request Assignee', - type: 'string', - description: 'User this merge request will be assigned to', - }, - reviewers: { - title: 'Merge Request Reviewers', - type: 'array', - items: { - type: 'string', - }, - description: 'Users that will be assigned as reviewers', - }, - assignReviewersFromApprovalRules: { - title: 'Assign reviewers from approval rules', - type: 'boolean', - description: - 'Automatically assign reviewers from the approval rules of the MR. Includes Codeowners', - }, - }, +Circumvent this behavior using \`.\``), + targetPath: z => + z + .string() + .optional() + .describe('Subdirectory of repository to apply changes to'), + token: z => + z + .string() + .optional() + .describe('The token to use for authorization to GitLab'), + commitAction: z => + z.enum(commitActions).optional().describe(`\ +The action to be used for \`git\` commit. Defaults to the custom \`auto\` action provided by Backstage, +which uses additional API calls in order to detect whether to \`create\`, \`update\` or \`skip\` each source file.`), + /** @deprecated projectID passed as query parameters in the repoUrl */ + projectid: z => + z + .string() + .optional() + .describe( + `\ +Project ID/Name(slug) of the GitLab Project +_deprecated_: \`projectid\` passed as query parameters in the \`repoUrl\``, + ), + removeSourceBranch: z => + z + .boolean() + .optional() + .describe( + 'Option to delete source branch once the MR has been merged. Default: `false`', + ), + assignee: z => + z + .string() + .optional() + .describe('User this merge request will be assigned to'), + reviewers: z => + z + .string() + .array() + .optional() + .describe('Users that will be assigned as reviewers'), + assignReviewersFromApprovalRules: z => + z + .boolean() + .optional() + .describe( + 'Automatically assign reviewers from the approval rules of the MR. Includes `CODEOWNERS`', + ), + labels: z => + z + .string() + .or(z.string().array()) + .optional() + .describe('Labels with which to tag the created merge request'), }, output: { - type: 'object', - properties: { - targetBranchName: { - title: 'Target branch name of the merge request', - type: 'string', - }, - projectid: { - title: 'Gitlab Project id/Name(slug)', - type: 'string', - }, - projectPath: { - title: 'Gitlab Project path', - type: 'string', - }, - mergeRequestUrl: { - title: 'MergeRequest(MR) URL', - type: 'string', - description: 'Link to the merge request in GitLab', - }, - }, + targetBranchName: z => + z.string().describe('Target branch name of the merge request'), + projectid: z => z.string().describe('GitLab Project id/Name(slug)'), + projectPath: z => z.string().describe('GitLab Project path'), + mergeRequestUrl: z => + z.string().describe('Link to the merge request in GitLab'), }, }, async handler(ctx) { @@ -291,6 +251,7 @@ which uses additional API calls in order to detect whether to 'create', 'update' sourcePath, title, token, + labels, } = ctx.input; const { owner, repo, project } = parseRepoUrl(repoUrl, integrations); @@ -499,6 +460,7 @@ which uses additional API calls in order to detect whether to 'create', 'update' : false, assigneeId, reviewerIds, + labels, }, ); return { @@ -552,7 +514,7 @@ which uses additional API calls in order to detect whether to 'create', 'update' ctx.output('projectid', repoID); ctx.output('targetBranchName', targetBranch); ctx.output('projectPath', repoID); - ctx.output('mergeRequestUrl', mrWebUrl); + ctx.output('mergeRequestUrl', mrWebUrl as string); }, }); }; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts index b051004ccc..41a00bc985 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts @@ -21,28 +21,10 @@ import { ExpandedPipelineSchema, PipelineTriggerTokenSchema, } from '@gitbeaker/rest'; -import { z } from 'zod'; -import commonGitlabConfig from '../commonGitlabConfig'; import { getClient, parseRepoUrl } from '../util'; import { examples } from './gitlabPipelineTrigger.examples'; import { getErrorMessage } from './helpers'; -const pipelineInputProperties = z.object({ - projectId: z.number().describe('Project Id'), - tokenDescription: z.string().describe('Pipeline token description'), - branch: z.string().describe('Project branch'), - variables: z - .record(z.string(), z.string()) - .optional() - .describe( - 'A object/record of key-valued strings containing the pipeline variables.', - ), -}); - -const pipelineOutputProperties = z.object({ - pipelineUrl: z.string({ description: 'Pipeline Url' }), -}); - /** * Creates a `gitlab:pipeline:trigger` Scaffolder action. * @@ -58,15 +40,50 @@ export const createTriggerGitlabPipelineAction = (options: { description: 'Triggers a GitLab Pipeline.', examples, schema: { - input: commonGitlabConfig.merge(pipelineInputProperties), - output: pipelineOutputProperties, + input: { + repoUrl: z => + z.string({ + description: `Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where 'project_name' is the repository name and 'group_name' is a group or username`, + }), + token: z => + z + .string({ + description: 'The token to use for authorization to GitLab', + }) + .optional(), + projectId: z => + z.number({ + description: 'Project Id', + }), + tokenDescription: z => + z.string({ + description: 'Pipeline token description', + }), + branch: z => + z.string({ + description: 'Project branch', + }), + variables: z => + z + .record(z.string(), z.string(), { + description: + 'A object/record of key-valued strings containing the pipeline variables.', + }) + .optional(), + }, + output: { + pipelineUrl: z => + z.string({ + description: 'Pipeline Url', + }), + }, }, async handler(ctx) { let pipelineTriggerToken: string | undefined = undefined; let pipelineTriggerId: number | undefined = undefined; const { repoUrl, projectId, tokenDescription, token, branch, variables } = - commonGitlabConfig.merge(pipelineInputProperties).parse(ctx.input); + ctx.input; const { host } = parseRepoUrl(repoUrl, integrations); const api = getClient({ host, integrations, token }); @@ -118,12 +135,6 @@ export const createTriggerGitlabPipelineAction = (options: { ctx.output('pipelineUrl', pipelineTriggerResponse.web_url); } catch (error: any) { - if (error instanceof z.ZodError) { - // Handling Zod validation errors - throw new InputError(`Validation error: ${error.message}`, { - validationErrors: error.errors, - }); - } // Handling other errors throw new InputError( `Failed to trigger Pipeline: ${getErrorMessage(error)}`, diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.ts index de248c56f7..47efbda306 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.ts @@ -19,7 +19,6 @@ import { ScmIntegrationRegistry } from '@backstage/integration'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { AccessTokenScopes, Gitlab } from '@gitbeaker/rest'; import { DateTime } from 'luxon'; -import { z } from 'zod'; import { getToken } from '../util'; import { examples } from './gitlabProjectAccessTokenCreate.examples'; @@ -38,39 +37,55 @@ export const createGitlabProjectAccessTokenAction = (options: { id: 'gitlab:projectAccessToken:create', examples, schema: { - input: z.object({ - projectId: z.union([z.number(), z.string()], { - description: 'Project ID/Name(slug) of the Gitlab Project', - }), - token: z - .string({ - description: 'The token to use for authorization to GitLab', - }) - .optional(), - name: z.string({ description: 'Name of Access Key' }).optional(), - repoUrl: z.string({ description: 'URL to gitlab instance' }), - accessLevel: z - .number({ - description: - 'Access Level of the Token, 10 (Guest), 20 (Reporter), 30 (Developer), 40 (Maintainer), and 50 (Owner)', - }) - .optional(), - scopes: z - .string({ - description: 'Scopes for a project access token', - }) - .array() - .optional(), - expiresAt: z - .string({ - description: - 'Expiration date of the access token in ISO format (YYYY-MM-DD). If Empty, it will set to the maximum of 365 days.', - }) - .optional(), - }), - output: z.object({ - access_token: z.string({ description: 'Access Token' }), - }), + input: { + projectId: z => + z.union([z.number(), z.string()], { + description: 'Project ID/Name(slug) of the Gitlab Project', + }), + token: z => + z + .string({ + description: 'The token to use for authorization to GitLab', + }) + .optional(), + name: z => + z + .string({ + description: 'Name of Access Key', + }) + .optional(), + repoUrl: z => + z.string({ + description: 'URL to gitlab instance', + }), + accessLevel: z => + z + .number({ + description: + 'Access Level of the Token, 10 (Guest), 20 (Reporter), 30 (Developer), 40 (Maintainer), and 50 (Owner)', + }) + .optional(), + scopes: z => + z + .string({ + description: 'Scopes for a project access token', + }) + .array() + .optional(), + expiresAt: z => + z + .string({ + description: + 'Expiration date of the access token in ISO format (YYYY-MM-DD). If Empty, it will set to the maximum of 365 days.', + }) + .optional(), + }, + output: { + access_token: z => + z.string({ + description: 'Access Token', + }), + }, }, async handler(ctx) { ctx.logger.info(`Creating Token for Project "${ctx.input.projectId}"`); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectDeployTokenCreate.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectDeployTokenCreate.ts index 26cf36d6c1..ad7f98483d 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectDeployTokenCreate.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectDeployTokenCreate.ts @@ -18,8 +18,6 @@ import { InputError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { DeployTokenScope, Gitlab } from '@gitbeaker/rest'; -import { z } from 'zod'; -import commonGitlabConfig from '../commonGitlabConfig'; import { getToken } from '../util'; import { examples } from './gitlabProjectDeployTokenCreate.examples'; @@ -37,22 +35,46 @@ export const createGitlabProjectDeployTokenAction = (options: { id: 'gitlab:projectDeployToken:create', examples, schema: { - input: commonGitlabConfig.merge( - z.object({ - projectId: z.union([z.number(), z.string()], { + input: { + repoUrl: z => + z.string({ + description: `Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where 'project_name' is the repository name and 'group_name' is a group or username`, + }), + token: z => + z + .string({ + description: 'The token to use for authorization to GitLab', + }) + .optional(), + projectId: z => + z.union([z.number(), z.string()], { description: 'Project ID', }), - name: z.string({ description: 'Deploy Token Name' }), - username: z - .string({ description: 'Deploy Token Username' }) + name: z => + z.string({ + description: 'Deploy Token Name', + }), + username: z => + z + .string({ + description: 'Deploy Token Username', + }) .optional(), - scopes: z.array(z.string(), { description: 'Scopes' }), - }), - ), - output: z.object({ - deploy_token: z.string({ description: 'Deploy Token' }), - user: z.string({ description: 'User' }), - }), + scopes: z => + z.array(z.string(), { + description: 'Scopes', + }), + }, + output: { + deploy_token: z => + z.string({ + description: 'Deploy Token', + }), + user: z => + z.string({ + description: 'User', + }), + }, }, async handler(ctx) { ctx.logger.info(`Creating Token for Project "${ctx.input.projectId}"`); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectMigrate.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectMigrate.examples.test.ts index d577adc896..df99638481 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectMigrate.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectMigrate.examples.test.ts @@ -39,7 +39,7 @@ jest.mock('@gitbeaker/rest', () => ({ })); describe('gitlab:group:migrate', () => { - let instance: TemplateAction; + let instance: TemplateAction; beforeEach(() => { jest.clearAllMocks(); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectMigrate.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectMigrate.test.ts index e1bf9af138..8163d83599 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectMigrate.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectMigrate.test.ts @@ -37,7 +37,7 @@ jest.mock('@gitbeaker/rest', () => ({ })); describe('createGitlabRepoMigrate', () => { - let instance: TemplateAction; + let instance: TemplateAction; beforeEach(() => { jest.clearAllMocks(); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectMigrate.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectMigrate.ts index b248210c76..5de449dba8 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectMigrate.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectMigrate.ts @@ -34,66 +34,42 @@ export const createGitlabProjectMigrateAction = (options: { }) => { const { integrations } = options; - return createTemplateAction<{ - destinationAccessToken: string; - destinationUrl: string; - sourceAccessToken: string; - sourceFullPath: string; - sourceUrl: string; - }>({ + return createTemplateAction({ id: 'gitlab:group:migrate', examples, schema: { input: { - required: [ - 'destinationAccessToken', - 'destinationUrl', - 'sourceAccessToken', - 'sourceFullPath', - 'sourceUrl', - ], - type: 'object', - properties: { - destinationAccessToken: { - type: 'string', - title: 'Target Repository Access Token', + destinationAccessToken: z => + z.string({ description: `The token to use for authorization to the target GitLab'`, - }, - destinationUrl: { - type: 'string', - title: 'Target Project Location', + }), + destinationUrl: z => + z.string({ description: `Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where 'project_name' is the repository name and 'group_name' is a group or username`, - }, - sourceAccessToken: { - type: 'string', - title: 'Source Group Access Token', + }), + sourceAccessToken: z => + z.string({ description: `The token to use for authorization to the source GitLab'`, - }, - sourceFullPath: { - type: 'string', - title: 'Group Full Path', + }), + sourceFullPath: z => + z.string({ description: 'Full path to the project in the source Gitlab instance', - }, - sourceUrl: { - type: 'string', - title: 'Source URL Location', + }), + sourceUrl: z => + z.string({ description: `Accepts the format 'https://gitlab.com/'`, - }, - }, + }), }, output: { - type: 'object', - properties: { - importedRepoUrl: { - title: 'URL to the newly imported repo', - type: 'string', - }, - migrationId: { - title: 'Id of the migration that imports the project', - type: 'number', - }, - }, + importedRepoUrl: z => + z.string({ + description: 'URL to the newly imported repo', + }), + migrationId: z => + z.number({ + description: 'Id of the migration that imports the project', + }), }, }, diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.ts index cab4410cc6..824a8566f7 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.ts @@ -17,8 +17,6 @@ import { ScmIntegrationRegistry } from '@backstage/integration'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { VariableType } from '@gitbeaker/rest'; -import { z } from 'zod'; -import commonGitlabConfig from '../commonGitlabConfig'; import { getClient, parseRepoUrl } from '../util'; import { examples } from './gitlabProjectVariableCreate.examples'; @@ -36,39 +34,65 @@ export const createGitlabProjectVariableAction = (options: { id: 'gitlab:projectVariable:create', examples, schema: { - input: commonGitlabConfig.merge( - z.object({ - projectId: z.union([z.number(), z.string()], { + input: { + repoUrl: z => + z.string({ + description: `Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where 'project_name' is the repository name and 'group_name' is a group or username`, + }), + token: z => + z + .string({ + description: 'The token to use for authorization to GitLab', + }) + .optional(), + projectId: z => + z.union([z.number(), z.string()], { description: 'Project ID', }), - key: z + key: z => + z .string({ description: 'The key of a variable; must have no more than 255 characters; only A-Z, a-z, 0-9, and _ are allowed', }) .regex(/^[A-Za-z0-9_]{1,255}$/), - value: z.string({ description: 'The value of a variable' }), - variableType: z.string({ + value: z => + z.string({ + description: 'The value of a variable', + }), + variableType: z => + z.string({ description: 'Variable Type (env_var or file)', }), - variableProtected: z - .boolean({ description: 'Whether the variable is protected' }) + variableProtected: z => + z + .boolean({ + description: 'Whether the variable is protected', + }) .default(false) .optional(), - masked: z - .boolean({ description: 'Whether the variable is masked' }) + masked: z => + z + .boolean({ + description: 'Whether the variable is masked', + }) .default(false) .optional(), - raw: z - .boolean({ description: 'Whether the variable is expandable' }) + raw: z => + z + .boolean({ + description: 'Whether the variable is expandable', + }) .default(false) .optional(), - environmentScope: z - .string({ description: 'The environment_scope of the variable' }) + environmentScope: z => + z + .string({ + description: 'The environment_scope of the variable', + }) .default('*') .optional(), - }), - ), + }, }, async handler(ctx) { const { diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.examples.test.ts index c78950a0b3..25b6c89ae4 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.examples.test.ts @@ -52,7 +52,7 @@ jest.mock('@gitbeaker/rest', () => ({ })); describe('gitlab:repo:push', () => { - let instance: TemplateAction; + let instance: TemplateAction; const mockDir = createMockDirectory(); const workspacePath = mockDir.resolve('workspace'); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.test.ts index 35a0b83ae5..8005c0791b 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.test.ts @@ -50,7 +50,7 @@ jest.mock('@gitbeaker/rest', () => ({ })); describe('createGitLabCommit', () => { - let instance: TemplateAction; + let instance: TemplateAction; const mockDir = createMockDirectory(); const workspacePath = mockDir.resolve('workspace'); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts index 9ac1de13d3..a3ae415704 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts @@ -14,16 +14,16 @@ * limitations under the License. */ +import path from 'path'; +import { ScmIntegrationRegistry } from '@backstage/integration'; +import { InputError } from '@backstage/errors'; +import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { createTemplateAction, parseRepoUrl, serializeDirectoryContents, } from '@backstage/plugin-scaffolder-node'; import { CommitAction } from '@gitbeaker/rest'; -import path from 'path'; -import { ScmIntegrationRegistry } from '@backstage/integration'; -import { InputError } from '@backstage/errors'; -import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { createGitlabApi, getErrorMessage } from './helpers'; import { examples } from './gitlabRepoPush.examples'; @@ -37,78 +37,63 @@ export const createGitlabRepoPushAction = (options: { }) => { const { integrations } = options; - return createTemplateAction<{ - repoUrl: string; - branchName: string; - commitMessage: string; - sourcePath?: string; - targetPath?: string; - token?: string; - commitAction?: 'create' | 'delete' | 'update'; - }>({ + return createTemplateAction({ id: 'gitlab:repo:push', examples, schema: { input: { - required: ['repoUrl', 'branchName', 'commitMessage'], - type: 'object', - properties: { - repoUrl: { - type: 'string', - title: 'Repository Location', + repoUrl: z => + z.string({ description: `Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where 'project_name' is the repository name and 'group_name' is a group or username`, - }, - branchName: { - type: 'string', - title: 'Source Branch Name', + }), + branchName: z => + z.string({ description: 'The branch name for the commit', - }, - commitMessage: { - type: 'string', - title: 'Commit Message', + }), + commitMessage: z => + z.string({ description: `The commit message`, - }, - sourcePath: { - type: 'string', - title: 'Working Subdirectory', - description: - 'Subdirectory of working directory to copy changes from', - }, - targetPath: { - type: 'string', - title: 'Repository Subdirectory', - description: 'Subdirectory of repository to apply changes to', - }, - token: { - title: 'Authentication Token', - type: 'string', - description: 'The token to use for authorization to GitLab', - }, - commitAction: { - title: 'Commit action', - type: 'string', - enum: ['create', 'update', 'delete'], - description: - 'The action to be used for git commit. Defaults to create, but can be set to update or delete', - }, - }, + }), + sourcePath: z => + z + .string({ + description: + 'Subdirectory of working directory to copy changes from', + }) + .optional(), + targetPath: z => + z + .string({ + description: 'Subdirectory of repository to apply changes to', + }) + .optional(), + token: z => + z + .string({ + description: 'The token to use for authorization to GitLab', + }) + .optional(), + commitAction: z => + z + .enum(['create', 'update', 'delete'], { + description: + 'The action to be used for git commit. Defaults to create, but can be set to update or delete', + }) + .optional(), }, output: { - type: 'object', - properties: { - projectid: { - title: 'Gitlab Project id/Name(slug)', - type: 'string', - }, - projectPath: { - title: 'Gitlab Project path', - type: 'string', - }, - commitHash: { - title: 'The git commit hash of the commit', - type: 'string', - }, - }, + projectid: z => + z.string({ + description: 'Gitlab Project id/Name(slug)', + }), + projectPath: z => + z.string({ + description: 'Gitlab Project path', + }), + commitHash: z => + z.string({ + description: 'The git commit hash of the commit', + }), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md index cb94aacbdd..cc8b465587 100644 --- a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-scaffolder-backend-module-notifications +## 0.1.11-next.1 + +### Patch Changes + +- 1fb5f06: Adds ability for user to turn on/off notifications for specific topics within an origin. +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/plugin-notifications-common@0.0.9-next.0 + - @backstage/plugin-notifications-node@0.2.16-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + +## 0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-notifications-node@0.2.16-next.0 + ## 0.1.10 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-notifications/package.json b/plugins/scaffolder-backend-module-notifications/package.json index 51f62ce8dc..a89f504f96 100644 --- a/plugins/scaffolder-backend-module-notifications/package.json +++ b/plugins/scaffolder-backend-module-notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-notifications", - "version": "0.1.10", + "version": "0.1.11-next.1", "description": "The notifications backend module for the scaffolder plugin.", "backstage": { "role": "backend-plugin-module", @@ -37,7 +37,6 @@ "@backstage/plugin-notifications-common": "workspace:^", "@backstage/plugin-notifications-node": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", - "octokit": "^3.0.0", "yaml": "^2.0.0" }, "devDependencies": { diff --git a/plugins/scaffolder-backend-module-notifications/report.api.md b/plugins/scaffolder-backend-module-notifications/report.api.md index 1d437cebd7..9c015e56e6 100644 --- a/plugins/scaffolder-backend-module-notifications/report.api.md +++ b/plugins/scaffolder-backend-module-notifications/report.api.md @@ -19,6 +19,7 @@ export function createSendNotificationAction(options: { link?: string | undefined; severity?: 'normal' | 'high' | 'low' | 'critical' | undefined; scope?: string | undefined; + topic?: string | undefined; optional?: boolean | undefined; }, { diff --git a/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.ts b/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.ts index a8e86f9b48..43a9201950 100644 --- a/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.ts +++ b/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.ts @@ -56,6 +56,7 @@ export function createSendNotificationAction(options: { .optional() .describe('Notification severity'), scope: z => z.string().optional().describe('Notification scope'), + topic: z => z.string().optional().describe('Notification topic'), optional: z => z .boolean() @@ -73,6 +74,7 @@ export function createSendNotificationAction(options: { info, link, severity, + topic, scope, optional, } = ctx.input; @@ -94,6 +96,7 @@ export function createSendNotificationAction(options: { description: info, link, severity, + topic, scope, }; diff --git a/plugins/scaffolder-backend-module-rails/CHANGELOG.md b/plugins/scaffolder-backend-module-rails/CHANGELOG.md index c646141f97..7aeb534e06 100644 --- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-scaffolder-backend-module-rails +## 0.5.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/types@1.2.1 + +## 0.5.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + ## 0.5.9 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index 1ff9cc56d2..f2293a966a 100644 --- a/plugins/scaffolder-backend-module-rails/package.json +++ b/plugins/scaffolder-backend-module-rails/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-rails", - "version": "0.5.9", + "version": "0.5.10-next.1", "description": "A module for the scaffolder backend that lets you template projects using Rails", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-rails/report.api.md b/plugins/scaffolder-backend-module-rails/report.api.md index 0d7618852d..a8abcfee1e 100644 --- a/plugins/scaffolder-backend-module-rails/report.api.md +++ b/plugins/scaffolder-backend-module-rails/report.api.md @@ -4,7 +4,6 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; -import { JsonObject } from '@backstage/types'; import { ScmIntegrations } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; import { UrlReaderService } from '@backstage/backend-plugin-api'; @@ -45,12 +44,51 @@ export function createFetchRailsAction(options: { }): TemplateAction< { url: string; - targetPath?: string; - values: JsonObject; - imageName?: string; + values: { + railsArguments?: + | { + template?: string | undefined; + database?: + | 'sqlite3' + | 'mysql' + | 'postgresql' + | 'oracle' + | 'sqlserver' + | 'jdbcmysql' + | 'jdbcsqlite3' + | 'jdbcpostgresql' + | 'jdbc' + | undefined; + api?: boolean | undefined; + force?: boolean | undefined; + minimal?: boolean | undefined; + railsVersion?: 'edge' | 'master' | 'dev' | 'fromImage' | undefined; + skipActionCable?: boolean | undefined; + skipActionMailbox?: boolean | undefined; + skipActionMailer?: boolean | undefined; + skipActionText?: boolean | undefined; + skipActiveStorage?: boolean | undefined; + skipBundle?: boolean | undefined; + skipTest?: boolean | undefined; + skipWebpackInstall?: boolean | undefined; + skipActiveRecord?: boolean | undefined; + webpacker?: + | 'react' + | 'angular' + | 'vue' + | 'elm' + | 'stimulus' + | undefined; + } + | undefined; + }; + targetPath?: string | undefined; + imageName?: string | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public diff --git a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts index fc9443efc0..aaff033c21 100644 --- a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts +++ b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts @@ -56,7 +56,9 @@ describe('fetch:rails', () => { url: 'https://rubyonrails.org/generator', targetPath: 'something', values: { - help: 'me', + railsArguments: { + minimal: true, + }, }, }, templateInfo: { diff --git a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts index 0e27810db3..ca8416654a 100644 --- a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts +++ b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { JsonObject } from '@backstage/types'; import { InputError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; import fs from 'fs-extra'; @@ -51,124 +50,104 @@ export function createFetchRailsAction(options: { }) { const { reader, integrations, containerRunner } = options; - return createTemplateAction<{ - url: string; - targetPath?: string; - values: JsonObject; - imageName?: string; - }>({ + return createTemplateAction({ id: 'fetch:rails', description: 'Downloads a template from the given `url` into the workspace, and runs a rails new generator on it.', examples, schema: { input: { - type: 'object', - required: ['url'], - properties: { - url: { - title: 'Fetch URL', + url: z => + z.string({ description: 'Relative path or absolute URL pointing to the directory tree to fetch', - type: 'string', - }, - targetPath: { - title: 'Target Path', - description: - 'Target path within the working directory to download the contents to.', - type: 'string', - }, - values: { - title: 'Template Values', - description: 'Values to pass on to rails for templating', - type: 'object', - properties: { - railsArguments: { - title: 'Arguments to pass to new command', - description: - 'You can provide some arguments to create a custom app', - type: 'object', - properties: { - minimal: { - title: 'minimal', + }), + targetPath: z => + z + .string({ + description: + 'Target path within the working directory to download the contents to.', + }) + .optional(), + values: z => + z.object({ + railsArguments: z + .object({ + minimal: z + .boolean({ description: 'Preconfigure a minimal rails app', - type: 'boolean', - }, - skipBundle: { - title: 'skipBundle', + }) + .optional(), + skipBundle: z + .boolean({ description: "Don't run bundle install", - type: 'boolean', - }, - skipWebpackInstall: { - title: 'skipWebpackInstall', + }) + .optional(), + skipWebpackInstall: z + .boolean({ description: "Don't run Webpack install", - type: 'boolean', - }, - skipTest: { - title: 'skipTest', + }) + .optional(), + skipTest: z + .boolean({ description: 'Skip test files', - type: 'boolean', - }, - skipActionCable: { - title: 'skipActionCable', + }) + .optional(), + skipActionCable: z + .boolean({ description: 'Skip Action Cable files', - type: 'boolean', - }, - skipActionMailer: { - title: 'skipActionMailer', + }) + .optional(), + skipActionMailer: z + .boolean({ description: 'Skip Action Mailer files', - type: 'boolean', - }, - skipActionMailbox: { - title: 'skipActionMailbox', + }) + .optional(), + skipActionMailbox: z + .boolean({ description: 'Skip Action Mailbox gem', - type: 'boolean', - }, - skipActiveStorage: { - title: 'skipActiveStorage', + }) + .optional(), + skipActiveStorage: z + .boolean({ description: 'Skip Active Storage files', - type: 'boolean', - }, - skipActionText: { - title: 'skipActionText', + }) + .optional(), + skipActionText: z + .boolean({ description: 'Skip Action Text gem', - type: 'boolean', - }, - skipActiveRecord: { - title: 'skipActiveRecord', + }) + .optional(), + skipActiveRecord: z + .boolean({ description: 'Skip Active Record files', - type: 'boolean', - }, - - force: { - title: 'force', + }) + .optional(), + force: z + .boolean({ description: 'Overwrite files that already exist', - type: 'boolean', - }, - api: { - title: 'api', + }) + .optional(), + api: z + .boolean({ description: 'Preconfigure smaller stack for API only apps', - type: 'boolean', - }, - template: { - title: 'template', + }) + .optional(), + template: z + .string({ description: 'Path to some application template (can be a filesystem path or URL)', - type: 'string', - }, - webpacker: { - title: 'webpacker', + }) + .optional(), + webpacker: z + .enum(['react', 'vue', 'angular', 'elm', 'stimulus'], { description: 'Preconfigure Webpack with a particular framework (options: react, vue, angular, elm, stimulus)', - type: 'string', - enum: ['react', 'vue', 'angular', 'elm', 'stimulus'], - }, - database: { - title: 'database', - description: - 'Preconfigure for selected database (options: mysql/postgresql/sqlite3/oracle/sqlserver/jdbcmysql/jdbcsqlite3/jdbcpostgresql/jdbc)', - type: 'string', - enum: [ + }) + .optional(), + database: z + .enum( + [ 'mysql', 'postgresql', 'sqlite3', @@ -179,25 +158,28 @@ export function createFetchRailsAction(options: { 'jdbcpostgresql', 'jdbc', ], - }, - railsVersion: { - title: 'Rails version in Gemfile', + { + description: + 'Preconfigure for selected database (options: mysql/postgresql/sqlite3/oracle/sqlserver/jdbcmysql/jdbcsqlite3/jdbcpostgresql/jdbc)', + }, + ) + .optional(), + railsVersion: z + .enum(['dev', 'edge', 'master', 'fromImage'], { description: 'Set up the application with Gemfile pointing to a specific version (options: fromImage, dev, edge, master)', - type: 'string', - enum: ['dev', 'edge', 'master', 'fromImage'], - }, - }, - }, - }, - }, - imageName: { - title: 'Rails Docker image', - description: - 'Specify a Docker image to run rails new. Used only when a local rails is not found.', - type: 'string', - }, - }, + }) + .optional(), + }) + .optional(), + }), + imageName: z => + z + .string({ + description: + 'Specify a Docker image to run rails new. Used only when a local rails is not found.', + }) + .optional(), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md index 2526b34ac6..433f6060d8 100644 --- a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-scaffolder-backend-module-sentry +## 0.2.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## 0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + ## 0.2.9 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-sentry/package.json b/plugins/scaffolder-backend-module-sentry/package.json index 90ab9f8bc5..b5b56d213f 100644 --- a/plugins/scaffolder-backend-module-sentry/package.json +++ b/plugins/scaffolder-backend-module-sentry/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-sentry", - "version": "0.2.9", + "version": "0.2.10-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-backend-module-sentry/report.api.md b/plugins/scaffolder-backend-module-sentry/report.api.md index 973ef9844c..8c716b3ab3 100644 --- a/plugins/scaffolder-backend-module-sentry/report.api.md +++ b/plugins/scaffolder-backend-module-sentry/report.api.md @@ -5,7 +5,6 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; -import { JsonObject } from '@backstage/types'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; // @public @@ -16,11 +15,13 @@ export function createSentryCreateProjectAction(options: { organizationSlug: string; teamSlug: string; name: string; - slug?: string; - authToken?: string; + slug?: string | undefined; + authToken?: string | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public diff --git a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts index 6c173deb59..226c31bd05 100644 --- a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts +++ b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts @@ -31,42 +31,36 @@ import { Config } from '@backstage/config'; export function createSentryCreateProjectAction(options: { config: Config }) { const { config } = options; - return createTemplateAction<{ - organizationSlug: string; - teamSlug: string; - name: string; - slug?: string; - authToken?: string; - }>({ + return createTemplateAction({ id: 'sentry:project:create', schema: { input: { - required: ['organizationSlug', 'teamSlug', 'name'], - type: 'object', - properties: { - organizationSlug: { - title: 'The slug of the organization the team belongs to', - type: 'string', - }, - teamSlug: { - title: 'The slug of the team to create a new project for', - type: 'string', - }, - name: { - title: 'The name for the new project', - type: 'string', - }, - slug: { - title: - 'Optional slug for the new project. If not provided a slug is generated from the name', - type: 'string', - }, - authToken: { - title: - 'authenticate via bearer auth token. Requires scope: project:write', - type: 'string', - }, - }, + organizationSlug: z => + z.string({ + description: 'The slug of the organization the team belongs to', + }), + teamSlug: z => + z.string({ + description: 'The slug of the team to create a new project for', + }), + name: z => + z.string({ + description: 'The name for the new project', + }), + slug: z => + z + .string({ + description: + 'Optional slug for the new project. If not provided a slug is generated from the name', + }) + .optional(), + authToken: z => + z + .string({ + description: + 'authenticate via bearer auth token. Requires scope: project:write', + }) + .optional(), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md index dc4d89d70a..5faceb7501 100644 --- a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-scaffolder-backend-module-yeoman +## 0.4.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/plugin-scaffolder-node-test-utils@0.2.3-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/types@1.2.1 + +## 0.4.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-scaffolder-node-test-utils@0.2.3-next.0 + ## 0.4.10 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index 76619cad6e..a121d7ef0c 100644 --- a/plugins/scaffolder-backend-module-yeoman/package.json +++ b/plugins/scaffolder-backend-module-yeoman/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-yeoman", - "version": "0.4.10", + "version": "0.4.11-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-backend-module-yeoman/report.api.md b/plugins/scaffolder-backend-module-yeoman/report.api.md index 57498bbe3a..cb4df446cb 100644 --- a/plugins/scaffolder-backend-module-yeoman/report.api.md +++ b/plugins/scaffolder-backend-module-yeoman/report.api.md @@ -4,18 +4,19 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; -import { JsonObject } from '@backstage/types'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; // @public export function createRunYeomanAction(): TemplateAction< { namespace: string; - args?: string[]; - options?: JsonObject; + args?: string[] | undefined; + options?: Record | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public diff --git a/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.ts b/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.ts index a8bbbf945d..aec9e3a415 100644 --- a/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.ts +++ b/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { JsonObject } from '@backstage/types'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { yeomanRun } from './yeomanRun'; import { examples } from './yeoman.examples'; @@ -29,38 +28,28 @@ import { examples } from './yeoman.examples'; * @public */ export function createRunYeomanAction() { - return createTemplateAction<{ - namespace: string; - args?: string[]; - options?: JsonObject; - }>({ + return createTemplateAction({ id: 'run:yeoman', description: 'Runs Yeoman on an installed Yeoman generator', examples, schema: { input: { - type: 'object', - required: ['namespace'], - properties: { - namespace: { - title: 'Generator Namespace', + namespace: z => + z.string({ description: 'Yeoman generator namespace, e.g: node:app', - type: 'string', - }, - args: { - title: 'Generator Arguments', - description: 'Arguments to pass on to Yeoman for templating', - type: 'array', - items: { - type: 'string', - }, - }, - options: { - title: 'Generator Options', - description: 'Options to pass on to Yeoman for templating', - type: 'object', - }, - }, + }), + args: z => + z + .array(z.string(), { + description: 'Arguments to pass on to Yeoman for templating', + }) + .optional(), + options: z => + z + .record(z.any(), { + description: 'Options to pass on to Yeoman for templating', + }) + .optional(), }, }, supportsDryRun: true, diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 5d23b4facb..629f1a23bd 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,115 @@ # @backstage/plugin-scaffolder-backend +## 2.0.0-next.1 + +### Major Changes + +- 33394db: **BREAKING CHANGES** + + Removal of deprecated re-exports from module packages. + + The following functions have been re-exported from the `scaffolder-backend` plugin for quite some time, and now it's time to clean them up. They've been moved as follows: + + - `createPublishAzureAction` should be imported from `@backstage/plugin-scaffolder-backend-module-azure` instead. + + - `createPublishBitbucketCloudAction` should be imported from `@backstage/plugin-scaffolder-backend-module-bitbucket-cloud` instead. + + - `createPublishBitbucketServerAction` and `createPublishBitbucketServerPullRequestAction` can be imported from `@backstage/plugin-scaffolder-backend-module-bitbucket-server` instead. + + - `createPublishBitbucketAction` should be imported from `@backstage/plugin-scaffolder-backend-module-bitbucket` instead. + + - `createPublishGerritAction` and `createPublishGerritReviewAction` can be imported from `@backstage/plugin-scaffolder-backend-module-gerrit` instead. + + - `createGithubActionsDispatchAction`, `createGithubDeployKeyAction`, `createGithubEnvironmentAction`, `createGithubIssuesLabelAction`, `CreateGithubPullRequestActionOptions`, `createGithubRepoCreateAction`, `createGithubRepoPushAction`, `createGithubWebhookAction`, and `createPublishGithubAction` can be imported from `@backstage/plugin-scaffolder-backend-module-github` instead. + + - `createPublishGitlabAction` should be imported from `@backstage/plugin-scaffolder-backend-module-gitlab` instead. + + - `ActionContext`. `createTemplateAction`, `executeShellCommand`, `ExecuteShellCommandOptions`, `fetchContents`, `TaskSecrets`, and `TemplateAction` should be imported from `@backstage/plugin-scaffolder-node` instead. + + - `ScaffolderEntitiesProcessor` should be imported from `@backstage/plugin-catalog-backend-module-scaffolder-entity-model` instead. + +- a8fcf04: **BREAKING ALPHA**: The `/alpha` export no longer exports the plugin. Please use `import('@backstage/plugin-scaffolder-backend')` instead as this has been removed. + + **BREAKING CHANGES**: The old `createRouter` function which was used in the old backend system has been removed along with the `RouterOptions` type. + +- 73b94d7: **BREAKING CHANGES** + + The following functions have been re-exported from the `scaffolder-backend` plugin for quite some time, and now it's time to clean them up. They've been moved as follows: + + - `SerializedTask`, `SerializedTaskEvent`, `TaskBroker`, `TaskBrokerDispatchOptions`, `TaskBrokerDispatchResult`, `TaskCompletionState`, `TaskContext`, `TaskEventType`, `TaskStatus`, `TemplateFilter`, and `TemplateGlobal` should be imported from `@backstage/plugin-scaffolder-node` instead. + + - The deprecated `copyWithoutRender` option has been removed from `fetch:template` action. You should rename the option to `copyWithoutTemplating` instead. + +### Minor Changes + +- 73b94d7: **DEPRECATIONS** + + The following types and implementations have been deprecated, either because they're no longer relevant, or because upcoming changes to the `scaffolder-backend` after `2.0.0` will influence the changes to these API surfaces. + + - `CreateWorkerOptions` + - `DatabaseTaskStore` + - `DatabaseTaskStoreOptions` + - `TaskManager` + - `TaskStoreCreateTaskOptions` + - `TaskStoreCreateTaskResult` + - `TaskStoreEmitOptions` + - `TaskStoreListEventsOptions` + - `TaskStoreRecoverTaskOptions` + - `TaskStoreShutDownTaskOptions` + + There is no current path off deprecation, these types are going to be removed and rethought with a better way to define workers in the new backend system. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.10-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.10-next.1 + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.10-next.1 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.10-next.1 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.10-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.11-next.1 + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.7.2-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.2-next.1 + - @backstage/backend-defaults@0.10.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/types@1.2.1 + - @backstage/plugin-bitbucket-cloud-common@0.3.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.9-next.1 + - @backstage/plugin-events-node@0.4.12-next.1 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-permission-node@0.10.1-next.1 + - @backstage/plugin-scaffolder-common@1.5.11 + +## 1.33.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.10-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.10-next.0 + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.2-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/backend-defaults@0.10.1-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.11-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.10-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.10-next.0 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.10-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.7.2-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.9-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + - @backstage/plugin-permission-node@0.10.1-next.0 + ## 1.33.0 ### Minor Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 6e6f3a7207..d3db857d91 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "1.33.0", + "version": "2.0.0-next.1", "description": "The Backstage backend plugin that helps you create new things", "backstage": { "role": "backend-plugin", @@ -64,7 +64,6 @@ "@backstage/backend-common": "^0.25.0", "@backstage/backend-defaults": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", @@ -88,7 +87,6 @@ "@backstage/plugin-scaffolder-node": "workspace:^", "@backstage/types": "workspace:^", "@opentelemetry/api": "^1.9.0", - "@types/express": "^4.17.6", "@types/luxon": "^3.0.0", "concat-stream": "^2.0.0", "express": "^4.17.1", @@ -122,6 +120,7 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/plugin-scaffolder-node-test-utils": "workspace:^", + "@types/express": "^4.17.6", "@types/fs-extra": "^11.0.0", "@types/nunjucks": "^3.1.4", "@types/supertest": "^2.0.8", diff --git a/plugins/scaffolder-backend/report-alpha.api.md b/plugins/scaffolder-backend/report-alpha.api.md index c35b743f6a..958d68fc25 100644 --- a/plugins/scaffolder-backend/report-alpha.api.md +++ b/plugins/scaffolder-backend/report-alpha.api.md @@ -3,7 +3,6 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeature } from '@backstage/backend-plugin-api'; import { ConditionalPolicyDecision } from '@backstage/plugin-permission-common'; import { Conditions } from '@backstage/plugin-permission-node'; import { JsonObject } from '@backstage/types'; @@ -26,10 +25,6 @@ export const createScaffolderTemplateConditionalDecision: ( conditions: PermissionCriteria>, ) => ConditionalPolicyDecision; -// @alpha (undocumented) -const _feature: BackendFeature; -export default _feature; - // @alpha export const scaffolderActionConditions: Conditions<{ hasActionId: PermissionRule< diff --git a/plugins/scaffolder-backend/report.api.md b/plugins/scaffolder-backend/report.api.md index 41c228e3f1..1f2a17a384 100644 --- a/plugins/scaffolder-backend/report.api.md +++ b/plugins/scaffolder-backend/report.api.md @@ -3,84 +3,44 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { ActionContext as ActionContext_2 } from '@backstage/plugin-scaffolder-node'; import { AuditorService } from '@backstage/backend-plugin-api'; import { AuthService } from '@backstage/backend-plugin-api'; -import { AutocompleteHandler } from '@backstage/plugin-scaffolder-node/alpha'; import { BackendFeature } from '@backstage/backend-plugin-api'; import { BackstageCredentials } from '@backstage/backend-plugin-api'; -import { CatalogApi } from '@backstage/catalog-client'; +import { CatalogService } from '@backstage/plugin-catalog-node'; import { Config } from '@backstage/config'; -import { CreatedTemplateFilter } from '@backstage/plugin-scaffolder-node/alpha'; -import { CreatedTemplateGlobal } from '@backstage/plugin-scaffolder-node/alpha'; -import { createGithubActionsDispatchAction as createGithubActionsDispatchAction_2 } from '@backstage/plugin-scaffolder-backend-module-github'; -import { createGithubDeployKeyAction as createGithubDeployKeyAction_2 } from '@backstage/plugin-scaffolder-backend-module-github'; -import { createGithubEnvironmentAction as createGithubEnvironmentAction_2 } from '@backstage/plugin-scaffolder-backend-module-github'; -import { createGithubIssuesLabelAction as createGithubIssuesLabelAction_2 } from '@backstage/plugin-scaffolder-backend-module-github'; -import { CreateGithubPullRequestActionOptions as CreateGithubPullRequestActionOptions_2 } from '@backstage/plugin-scaffolder-backend-module-github'; -import { createGithubRepoCreateAction as createGithubRepoCreateAction_2 } from '@backstage/plugin-scaffolder-backend-module-github'; -import { createGithubRepoPushAction as createGithubRepoPushAction_2 } from '@backstage/plugin-scaffolder-backend-module-github'; -import { createGithubWebhookAction as createGithubWebhookAction_2 } from '@backstage/plugin-scaffolder-backend-module-github'; -import { createPublishAzureAction as createPublishAzureAction_2 } from '@backstage/plugin-scaffolder-backend-module-azure'; -import { createPublishBitbucketAction as createPublishBitbucketAction_2 } from '@backstage/plugin-scaffolder-backend-module-bitbucket'; -import { createPublishBitbucketCloudAction as createPublishBitbucketCloudAction_2 } from '@backstage/plugin-scaffolder-backend-module-bitbucket-cloud'; -import { createPublishBitbucketServerAction as createPublishBitbucketServerAction_2 } from '@backstage/plugin-scaffolder-backend-module-bitbucket-server'; -import { createPublishBitbucketServerPullRequestAction as createPublishBitbucketServerPullRequestAction_2 } from '@backstage/plugin-scaffolder-backend-module-bitbucket-server'; -import { createPublishGerritAction as createPublishGerritAction_2 } from '@backstage/plugin-scaffolder-backend-module-gerrit'; -import { createPublishGerritReviewAction as createPublishGerritReviewAction_2 } from '@backstage/plugin-scaffolder-backend-module-gerrit'; -import { createPublishGithubAction as createPublishGithubAction_2 } from '@backstage/plugin-scaffolder-backend-module-github'; -import { createPublishGitlabAction as createPublishGitlabAction_2 } from '@backstage/plugin-scaffolder-backend-module-gitlab'; -import { createTemplateAction as createTemplateAction_2 } from '@backstage/plugin-scaffolder-node'; import { DatabaseService } from '@backstage/backend-plugin-api'; -import { DiscoveryService } from '@backstage/backend-plugin-api'; import { Duration } from 'luxon'; import { EventsService } from '@backstage/plugin-events-node'; -import { executeShellCommand as executeShellCommand_2 } from '@backstage/plugin-scaffolder-node'; -import { ExecuteShellCommandOptions } from '@backstage/plugin-scaffolder-node'; -import express from 'express'; -import { fetchContents as fetchContents_2 } from '@backstage/plugin-scaffolder-node'; -import { HttpAuthService } from '@backstage/backend-plugin-api'; import { HumanDuration } from '@backstage/types'; -import { IdentityApi } from '@backstage/plugin-auth-node'; import { JsonObject } from '@backstage/types'; import { JsonValue } from '@backstage/types'; import { Knex } from 'knex'; -import { LifecycleService } from '@backstage/backend-plugin-api'; -import { Logger } from 'winston'; +import { LoggerService } from '@backstage/backend-plugin-api'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { PermissionRule } from '@backstage/plugin-permission-node'; import { PermissionRuleParams } from '@backstage/plugin-permission-common'; -import { PermissionsService } from '@backstage/backend-plugin-api'; import { RESOURCE_TYPE_SCAFFOLDER_ACTION } from '@backstage/plugin-scaffolder-common/alpha'; import { RESOURCE_TYPE_SCAFFOLDER_TEMPLATE } from '@backstage/plugin-scaffolder-common/alpha'; -import { ScaffolderEntitiesProcessor as ScaffolderEntitiesProcessor_2 } from '@backstage/plugin-catalog-backend-module-scaffolder-entity-model'; -import { SchedulerService } from '@backstage/backend-plugin-api'; -import { ScmIntegrationRegistry } from '@backstage/integration'; import { ScmIntegrations } from '@backstage/integration'; -import { SerializedTask as SerializedTask_2 } from '@backstage/plugin-scaffolder-node'; -import { SerializedTaskEvent as SerializedTaskEvent_2 } from '@backstage/plugin-scaffolder-node'; -import { TaskBroker as TaskBroker_2 } from '@backstage/plugin-scaffolder-node'; -import { TaskBrokerDispatchOptions as TaskBrokerDispatchOptions_2 } from '@backstage/plugin-scaffolder-node'; -import { TaskBrokerDispatchResult as TaskBrokerDispatchResult_2 } from '@backstage/plugin-scaffolder-node'; -import { TaskCompletionState as TaskCompletionState_2 } from '@backstage/plugin-scaffolder-node'; -import { TaskContext as TaskContext_2 } from '@backstage/plugin-scaffolder-node'; -import { TaskEventType as TaskEventType_2 } from '@backstage/plugin-scaffolder-node'; +import { SerializedTask } from '@backstage/plugin-scaffolder-node'; +import { SerializedTaskEvent } from '@backstage/plugin-scaffolder-node'; +import { TaskBroker } from '@backstage/plugin-scaffolder-node'; +import { TaskCompletionState } from '@backstage/plugin-scaffolder-node'; +import { TaskContext } from '@backstage/plugin-scaffolder-node'; import { TaskRecovery } from '@backstage/plugin-scaffolder-common'; -import { TaskSecrets as TaskSecrets_2 } from '@backstage/plugin-scaffolder-node'; +import { TaskSecrets } from '@backstage/plugin-scaffolder-node'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { TaskSpecV1beta3 } from '@backstage/plugin-scaffolder-common'; -import { TaskStatus as TaskStatus_2 } from '@backstage/plugin-scaffolder-node'; -import { TemplateAction as TemplateAction_2 } from '@backstage/plugin-scaffolder-node'; +import { TaskStatus } from '@backstage/plugin-scaffolder-node'; +import { TemplateAction } from '@backstage/plugin-scaffolder-node'; import { TemplateEntityStepV1beta3 } from '@backstage/plugin-scaffolder-common'; -import { TemplateFilter as TemplateFilter_2 } from '@backstage/plugin-scaffolder-node'; -import { TemplateGlobal as TemplateGlobal_2 } from '@backstage/plugin-scaffolder-node'; +import { TemplateFilter } from '@backstage/plugin-scaffolder-node'; +import { TemplateGlobal } from '@backstage/plugin-scaffolder-node'; import { TemplateParametersV1beta3 } from '@backstage/plugin-scaffolder-common'; import { UrlReaderService } from '@backstage/backend-plugin-api'; import { WorkspaceProvider } from '@backstage/plugin-scaffolder-node/alpha'; -// @public @deprecated (undocumented) -export type ActionContext = ActionContext_2; - // @public (undocumented) export type ActionPermissionRuleInput< TParams extends PermissionRuleParams = PermissionRuleParams, @@ -91,60 +51,63 @@ export type ActionPermissionRuleInput< TParams >; -// @public -export const createBuiltinActions: ( - options: CreateBuiltInActionsOptions, -) => TemplateAction_2[]; - -// @public -export interface CreateBuiltInActionsOptions { - additionalTemplateFilters?: Record; - // (undocumented) - additionalTemplateGlobals?: Record; - auth?: AuthService; - catalogClient: CatalogApi; - config: Config; - integrations: ScmIntegrations; - reader: UrlReaderService; -} - // @public export function createCatalogRegisterAction(options: { - catalogClient: CatalogApi; + catalog: CatalogService; integrations: ScmIntegrations; - auth?: AuthService; -}): TemplateAction_2< +}): TemplateAction< | { catalogInfoUrl: string; - optional?: boolean; + optional?: boolean | undefined; + } + | { + catalogInfoUrl: string; + optional?: boolean | undefined; + catalogInfoPath?: string | undefined; } | { repoContentsUrl: string; - catalogInfoPath?: string; - optional?: boolean; + optional?: boolean | undefined; + } + | { + repoContentsUrl: string; + optional?: boolean | undefined; + catalogInfoPath?: string | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public -export function createCatalogWriteAction(): TemplateAction_2< +export function createCatalogWriteAction(): TemplateAction< { entity: Record; filePath?: string | undefined; }, - any, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public -export function createDebugLogAction(): TemplateAction_2; +export function createDebugLogAction(): TemplateAction< + { + message?: string | undefined; + listWorkspace?: boolean | 'with-contents' | 'with-filenames' | undefined; + }, + { + [x: string]: any; + }, + 'v2' +>; // @public export function createFetchCatalogEntityAction(options: { - catalogClient: CatalogApi; - auth?: AuthService; -}): TemplateAction_2< + catalog: CatalogService; +}): TemplateAction< { entityRef?: string | undefined; entityRefs?: string[] | undefined; @@ -163,89 +126,99 @@ export function createFetchCatalogEntityAction(options: { export function createFetchPlainAction(options: { reader: UrlReaderService; integrations: ScmIntegrations; -}): TemplateAction_2< +}): TemplateAction< { url: string; - targetPath?: string; - token?: string; + targetPath?: string | undefined; + token?: string | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public export function createFetchPlainFileAction(options: { reader: UrlReaderService; integrations: ScmIntegrations; -}): TemplateAction_2< +}): TemplateAction< { url: string; targetPath: string; - token?: string; + token?: string | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public export function createFetchTemplateAction(options: { reader: UrlReaderService; integrations: ScmIntegrations; - additionalTemplateFilters?: Record; - additionalTemplateGlobals?: Record; -}): TemplateAction_2< + additionalTemplateFilters?: Record; + additionalTemplateGlobals?: Record; +}): TemplateAction< { url: string; - targetPath?: string; - values: any; - templateFileExtension?: string | boolean; - copyWithoutRender?: string[]; - copyWithoutTemplating?: string[]; - cookiecutterCompat?: boolean; - replace?: boolean; - trimBlocks?: boolean; - lstripBlocks?: boolean; - token?: string; + targetPath?: string | undefined; + values?: Record | undefined; + copyWithoutRender?: string[] | undefined; + copyWithoutTemplating?: string[] | undefined; + cookiecutterCompat?: boolean | undefined; + templateFileExtension?: string | boolean | undefined; + replace?: boolean | undefined; + trimBlocks?: boolean | undefined; + lstripBlocks?: boolean | undefined; + token?: string | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public export function createFetchTemplateFileAction(options: { reader: UrlReaderService; integrations: ScmIntegrations; - additionalTemplateFilters?: Record; - additionalTemplateGlobals?: Record; -}): TemplateAction_2< + additionalTemplateFilters?: Record; + additionalTemplateGlobals?: Record; +}): TemplateAction< { url: string; targetPath: string; - values: any; - cookiecutterCompat?: boolean; - replace?: boolean; - trimBlocks?: boolean; - lstripBlocks?: boolean; - token?: string; + values?: Record | undefined; + cookiecutterCompat?: boolean | undefined; + replace?: boolean | undefined; + trimBlocks?: boolean | undefined; + lstripBlocks?: boolean | undefined; + token?: string | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public -export const createFilesystemDeleteAction: () => TemplateAction_2< +export const createFilesystemDeleteAction: () => TemplateAction< { files: string[]; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public -export const createFilesystemReadDirAction: () => TemplateAction_2< +export const createFilesystemReadDirAction: () => TemplateAction< { - recursive: boolean; paths: string[]; + recursive: boolean; }, { files: { @@ -259,149 +232,50 @@ export const createFilesystemReadDirAction: () => TemplateAction_2< fullPath: string; }[]; }, - 'v1' + 'v2' >; // @public -export const createFilesystemRenameAction: () => TemplateAction_2< +export const createFilesystemRenameAction: () => TemplateAction< { - files: Array<{ + files: { from: string; to: string; - overwrite?: boolean; - }>; + overwrite?: boolean | undefined; + }[]; }, - JsonObject, - 'v1' ->; - -// @public @deprecated (undocumented) -export const createGithubActionsDispatchAction: typeof createGithubActionsDispatchAction_2; - -// @public @deprecated (undocumented) -export const createGithubDeployKeyAction: typeof createGithubDeployKeyAction_2; - -// @public @deprecated (undocumented) -export const createGithubEnvironmentAction: typeof createGithubEnvironmentAction_2; - -// @public @deprecated (undocumented) -export const createGithubIssuesLabelAction: typeof createGithubIssuesLabelAction_2; - -// @public @deprecated (undocumented) -export type CreateGithubPullRequestActionOptions = - CreateGithubPullRequestActionOptions_2; - -// @public @deprecated (undocumented) -export const createGithubRepoCreateAction: typeof createGithubRepoCreateAction_2; - -// @public @deprecated (undocumented) -export const createGithubRepoPushAction: typeof createGithubRepoPushAction_2; - -// @public @deprecated (undocumented) -export const createGithubWebhookAction: typeof createGithubWebhookAction_2; - -// @public @deprecated (undocumented) -export const createPublishAzureAction: typeof createPublishAzureAction_2; - -// @public @deprecated (undocumented) -export const createPublishBitbucketAction: typeof createPublishBitbucketAction_2; - -// @public @deprecated (undocumented) -export const createPublishBitbucketCloudAction: typeof createPublishBitbucketCloudAction_2; - -// @public @deprecated (undocumented) -export const createPublishBitbucketServerAction: typeof createPublishBitbucketServerAction_2; - -// @public @deprecated (undocumented) -export const createPublishBitbucketServerPullRequestAction: typeof createPublishBitbucketServerPullRequestAction_2; - -// @public @deprecated (undocumented) -export const createPublishGerritAction: typeof createPublishGerritAction_2; - -// @public @deprecated (undocumented) -export const createPublishGerritReviewAction: typeof createPublishGerritReviewAction_2; - -// @public @deprecated (undocumented) -export const createPublishGithubAction: typeof createPublishGithubAction_2; - -// @public @deprecated (undocumented) -export const createPublishGithubPullRequestAction: ( - options: CreateGithubPullRequestActionOptions_2, -) => TemplateAction_2< { - title: string; - branchName: string; - targetBranchName?: string; - description: string; - repoUrl: string; - draft?: boolean; - targetPath?: string; - sourcePath?: string; - token?: string; - reviewers?: string[]; - assignees?: string[]; - teamReviewers?: string[]; - commitMessage?: string; - update?: boolean; - forceFork?: boolean; - gitAuthorName?: string; - gitAuthorEmail?: string; - forceEmptyGitAuthor?: boolean; - createWhenEmpty?: boolean; + [x: string]: any; }, - JsonObject, - 'v1' + 'v2' >; -// @public @deprecated (undocumented) -export const createPublishGitlabAction: typeof createPublishGitlabAction_2; - -// @public @deprecated (undocumented) -export const createPublishGitlabMergeRequestAction: (options: { - integrations: ScmIntegrationRegistry; -}) => TemplateAction_2< - { - repoUrl: string; - title: string; - description: string; - branchName: string; - targetBranchName?: string; - sourcePath?: string; - targetPath?: string; - token?: string; - commitAction?: 'create' | 'delete' | 'update' | 'skip' | 'auto'; - projectid?: string; - removeSourceBranch?: boolean; - assignee?: string; - reviewers?: string[]; - assignReviewersFromApprovalRules?: boolean; - }, - JsonObject, - 'v1' ->; - -// @public @deprecated -export function createRouter(options: RouterOptions): Promise; - -// @public @deprecated (undocumented) -export const createTemplateAction: typeof createTemplateAction_2; - // @public export function createWaitAction(options?: { maxWaitTime?: Duration | HumanDuration; -}): TemplateAction_2; +}): TemplateAction< + { + minutes?: number | undefined; + seconds?: number | undefined; + milliseconds?: number | undefined; + }, + { + [x: string]: any; + }, + 'v2' +>; -// @public +// @public @deprecated export type CreateWorkerOptions = { - taskBroker: TaskBroker_2; + taskBroker: TaskBroker; actionRegistry: TemplateActionRegistry; integrations: ScmIntegrations; workingDirectory: string; - logger: Logger; + logger: LoggerService; auditor?: AuditorService; - additionalTemplateFilters?: Record; + additionalTemplateFilters?: Record; concurrentTasksLimit?: number; - additionalTemplateGlobals?: Record; + additionalTemplateGlobals?: Record; permissions?: PermissionEvaluator; gracefulShutdown?: boolean; }; @@ -409,14 +283,14 @@ export type CreateWorkerOptions = { // @public export interface CurrentClaimedTask { createdBy?: string; - secrets?: TaskSecrets_2; + secrets?: TaskSecrets; spec: TaskSpec; state?: JsonObject; taskId: string; workspace?: Promise; } -// @public +// @public @deprecated export class DatabaseTaskStore implements TaskStore { // (undocumented) cancelTask( @@ -427,13 +301,13 @@ export class DatabaseTaskStore implements TaskStore { >, ): Promise; // (undocumented) - claimTask(): Promise; + claimTask(): Promise; // (undocumented) cleanWorkspace({ taskId }: { taskId: string }): Promise; // (undocumented) completeTask(options: { taskId: string; - status: TaskStatus_2; + status: TaskStatus; eventBody: JsonObject; }): Promise; // (undocumented) @@ -451,7 +325,7 @@ export class DatabaseTaskStore implements TaskStore { >, ): Promise; // (undocumented) - getTask(taskId: string): Promise; + getTask(taskId: string): Promise; // (undocumented) getTaskState({ taskId }: { taskId: string }): Promise< | { @@ -464,10 +338,10 @@ export class DatabaseTaskStore implements TaskStore { // (undocumented) list(options: { createdBy?: string; - status?: TaskStatus_2; + status?: TaskStatus; filters?: { createdBy?: string | string[]; - status?: TaskStatus_2 | TaskStatus_2[]; + status?: TaskStatus | TaskStatus[]; }; pagination?: { limit?: number; @@ -478,12 +352,12 @@ export class DatabaseTaskStore implements TaskStore { field: string; }[]; }): Promise<{ - tasks: SerializedTask_2[]; + tasks: SerializedTask[]; totalTasks?: number; }>; // (undocumented) listEvents(options: TaskStoreListEventsOptions): Promise<{ - events: SerializedTaskEvent_2[]; + events: SerializedTaskEvent[]; }>; // (undocumented) listStaleTasks(options: { timeoutS: number }): Promise<{ @@ -502,10 +376,7 @@ export class DatabaseTaskStore implements TaskStore { targetPath: string; }): Promise; // (undocumented) - retryTask?(options: { - secrets?: TaskSecrets_2; - taskId: string; - }): Promise; + retryTask?(options: { secrets?: TaskSecrets; taskId: string }): Promise; // (undocumented) saveTaskState(options: { taskId: string; state?: JsonObject }): Promise; // (undocumented) @@ -514,121 +385,30 @@ export class DatabaseTaskStore implements TaskStore { shutdownTask(options: TaskStoreShutDownTaskOptions): Promise; } -// @public +// @public @deprecated export type DatabaseTaskStoreOptions = { database: DatabaseService | Knex; events?: EventsService; }; -// @public @deprecated -export const executeShellCommand: typeof executeShellCommand_2; - -// @public @deprecated -export const fetchContents: typeof fetchContents_2; - -// @public @deprecated -export interface RouterOptions { - // (undocumented) - actions?: TemplateAction_2[]; - // (undocumented) - additionalTemplateFilters?: - | Record - | CreatedTemplateFilter[]; - // (undocumented) - additionalTemplateGlobals?: - | Record - | CreatedTemplateGlobal[]; - // (undocumented) - additionalWorkspaceProviders?: Record; - // (undocumented) - auditor?: AuditorService; - // (undocumented) - auth?: AuthService; - // (undocumented) - autocompleteHandlers?: Record; - // (undocumented) - catalogClient: CatalogApi; - concurrentTasksLimit?: number; - // (undocumented) - config: Config; - // (undocumented) - database: DatabaseService; - // (undocumented) - discovery?: DiscoveryService; - // (undocumented) - events?: EventsService; - // (undocumented) - httpAuth?: HttpAuthService; - // (undocumented) - identity?: IdentityApi; - // (undocumented) - lifecycle?: LifecycleService; - // (undocumented) - logger: Logger; - // (undocumented) - permissionRules?: Array< - TemplatePermissionRuleInput | ActionPermissionRuleInput - >; - // (undocumented) - permissions?: PermissionsService; - // (undocumented) - reader: UrlReaderService; - // (undocumented) - scheduler?: SchedulerService; - // (undocumented) - taskBroker?: TaskBroker_2; - // @deprecated (undocumented) - taskWorkers?: number; -} - -// @public @deprecated -export type RunCommandOptions = ExecuteShellCommandOptions; - -// @public @deprecated -export const ScaffolderEntitiesProcessor: typeof ScaffolderEntitiesProcessor_2; - // @public const scaffolderPlugin: BackendFeature; export default scaffolderPlugin; // @public @deprecated -export type SerializedTask = SerializedTask_2; - -// @public @deprecated -export type SerializedTaskEvent = SerializedTaskEvent_2; - -// @public @deprecated -export type TaskBroker = TaskBroker_2; - -// @public @deprecated -export type TaskBrokerDispatchOptions = TaskBrokerDispatchOptions_2; - -// @public @deprecated -export type TaskBrokerDispatchResult = TaskBrokerDispatchResult_2; - -// @public @deprecated -export type TaskCompletionState = TaskCompletionState_2; - -// @public @deprecated -export type TaskContext = TaskContext_2; - -// @public @deprecated -export type TaskEventType = TaskEventType_2; - -// @public -export class TaskManager implements TaskContext_2 { +export class TaskManager implements TaskContext { // (undocumented) get cancelSignal(): AbortSignal; // (undocumented) cleanWorkspace?(): Promise; // (undocumented) - complete(result: TaskCompletionState_2, metadata?: JsonObject): Promise; + complete(result: TaskCompletionState, metadata?: JsonObject): Promise; // (undocumented) static create( task: CurrentClaimedTask, storage: TaskStore, abortSignal: AbortSignal, - logger: Logger, + logger: LoggerService, auth?: AuthService, config?: Config, additionalWorkspaceProviders?: Record, @@ -656,7 +436,7 @@ export class TaskManager implements TaskContext_2 { targetPath: string; }): Promise; // (undocumented) - get secrets(): TaskSecrets_2 | undefined; + get secrets(): TaskSecrets | undefined; // (undocumented) serializeWorkspace?(options: { path: string }): Promise; // (undocumented) @@ -677,13 +457,7 @@ export class TaskManager implements TaskContext_2 { ): Promise; } -// @public @deprecated (undocumented) -export type TaskSecrets = TaskSecrets_2; - // @public @deprecated -export type TaskStatus = TaskStatus_2; - -// @public export interface TaskStore { // (undocumented) cancelTask?(options: TaskStoreEmitOptions): Promise; @@ -772,10 +546,7 @@ export interface TaskStore { targetPath: string; }): Promise; // (undocumented) - retryTask?(options: { - secrets?: TaskSecrets_2; - taskId: string; - }): Promise; + retryTask?(options: { secrets?: TaskSecrets; taskId: string }): Promise; // (undocumented) saveTaskState?(options: { taskId: string; @@ -793,42 +564,42 @@ export interface TaskStore { shutdownTask?(options: TaskStoreShutDownTaskOptions): Promise; } -// @public +// @public @deprecated export type TaskStoreCreateTaskOptions = { spec: TaskSpec; createdBy?: string; - secrets?: TaskSecrets_2; + secrets?: TaskSecrets; }; -// @public +// @public @deprecated export type TaskStoreCreateTaskResult = { taskId: string; }; -// @public +// @public @deprecated export type TaskStoreEmitOptions = { taskId: string; body: TBody; }; -// @public +// @public @deprecated export type TaskStoreListEventsOptions = { isTaskRecoverable?: boolean; taskId: string; after?: number | undefined; }; -// @public +// @public @deprecated export type TaskStoreRecoverTaskOptions = { timeout: HumanDuration; }; -// @public +// @public @deprecated export type TaskStoreShutDownTaskOptions = { taskId: string; }; -// @public +// @public @deprecated export class TaskWorker { // (undocumented) static create(options: CreateWorkerOptions): Promise; @@ -837,33 +608,23 @@ export class TaskWorker { // (undocumented) recoverTasks(): Promise; // (undocumented) - runOneTask(task: TaskContext_2): Promise; + runOneTask(task: TaskContext): Promise; // (undocumented) start(): void; // (undocumented) stop(): Promise; } -// @public @deprecated (undocumented) -export type TemplateAction = - TemplateAction_2; - -// @public +// @public @deprecated export class TemplateActionRegistry { // (undocumented) - get(actionId: string): TemplateAction_2; + get(actionId: string): TemplateAction; // (undocumented) - list(): TemplateAction_2[]; + list(): TemplateAction[]; // (undocumented) - register(action: TemplateAction_2): void; + register(action: TemplateAction): void; } -// @public @deprecated (undocumented) -export type TemplateFilter = TemplateFilter_2; - -// @public @deprecated (undocumented) -export type TemplateGlobal = TemplateGlobal_2; - // @public (undocumented) export type TemplatePermissionRuleInput< TParams extends PermissionRuleParams = PermissionRuleParams, diff --git a/plugins/scaffolder-backend/sample-templates/notifications-demo/template.yaml b/plugins/scaffolder-backend/sample-templates/notifications-demo/template.yaml index a85f53b12f..abd18b1d42 100644 --- a/plugins/scaffolder-backend/sample-templates/notifications-demo/template.yaml +++ b/plugins/scaffolder-backend/sample-templates/notifications-demo/template.yaml @@ -50,6 +50,10 @@ spec: - normal - high - critical + topic: + title: Topic + type: string + description: Notification topic scope: title: Scope type: string @@ -66,4 +70,5 @@ spec: description: ${{ parameters.description }} link: ${{ parameters.link }} severity: ${{ parameters.severity }} + topic: ${{ parameters.topic }} scope: ${{ parameters.scope }} diff --git a/plugins/scaffolder-backend/src/ScaffolderPlugin.ts b/plugins/scaffolder-backend/src/ScaffolderPlugin.ts index 9fe78c7119..7ef3abcbe3 100644 --- a/plugins/scaffolder-backend/src/ScaffolderPlugin.ts +++ b/plugins/scaffolder-backend/src/ScaffolderPlugin.ts @@ -19,7 +19,7 @@ import { createBackendPlugin, } from '@backstage/backend-plugin-api'; import { ScmIntegrations } from '@backstage/integration'; -import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha'; +import { catalogServiceRef } from '@backstage/plugin-catalog-node'; import { eventsServiceRef } from '@backstage/plugin-events-node'; import { TaskBroker, TemplateAction } from '@backstage/plugin-scaffolder-node'; import { @@ -134,11 +134,10 @@ export const scaffolderPlugin = createBackendPlugin({ permissions: coreServices.permissions, database: coreServices.database, auth: coreServices.auth, - discovery: coreServices.discovery, httpRouter: coreServices.httpRouter, httpAuth: coreServices.httpAuth, auditor: coreServices.auditor, - catalogClient: catalogServiceRef, + catalog: catalogServiceRef, events: eventsServiceRef, }, async init({ @@ -148,10 +147,9 @@ export const scaffolderPlugin = createBackendPlugin({ reader, database, auth, - discovery, httpRouter, httpAuth, - catalogClient, + catalog, permissions, events, auditor, @@ -193,8 +191,8 @@ export const scaffolderPlugin = createBackendPlugin({ createDebugLogAction(), createWaitAction(), // todo(blam): maybe these should be a -catalog module? - createCatalogRegisterAction({ catalogClient, integrations, auth }), - createFetchCatalogEntityAction({ catalogClient, auth }), + createCatalogRegisterAction({ catalog, integrations }), + createFetchCatalogEntityAction({ catalog }), createCatalogWriteAction(), createFilesystemDeleteAction(), createFilesystemRenameAction(), @@ -208,11 +206,10 @@ export const scaffolderPlugin = createBackendPlugin({ ); const router = await createRouter({ - logger: log, + logger, config, database, - catalogClient, - reader, + catalog, lifecycle, actions, taskBroker, @@ -220,7 +217,6 @@ export const scaffolderPlugin = createBackendPlugin({ additionalTemplateGlobals, auth, httpAuth, - discovery, permissions, autocompleteHandlers, additionalWorkspaceProviders, diff --git a/plugins/scaffolder-backend/src/alpha.ts b/plugins/scaffolder-backend/src/alpha.ts index 7d1d8fe0ef..438d16303d 100644 --- a/plugins/scaffolder-backend/src/alpha.ts +++ b/plugins/scaffolder-backend/src/alpha.ts @@ -13,10 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { scaffolderPlugin } from './ScaffolderPlugin'; - -/** @alpha */ -const _feature = scaffolderPlugin; -export default _feature; - -export * from './service'; +export * from './service/alpha'; diff --git a/plugins/scaffolder-backend/src/deprecated.ts b/plugins/scaffolder-backend/src/deprecated.ts deleted file mode 100644 index a1bcc30564..0000000000 --- a/plugins/scaffolder-backend/src/deprecated.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - ActionContext as ActionContextNode, - createTemplateAction as createTemplateActionNode, - TaskSecrets as TaskSecretsNode, - TemplateAction as TemplateActionNode, - executeShellCommand as executeShellCommandNode, - ExecuteShellCommandOptions as ExecuteShellCommandOptionsNode, - fetchContents as fetchContentsNode, -} from '@backstage/plugin-scaffolder-node'; -import { JsonObject } from '@backstage/types'; -import { ScaffolderEntitiesProcessor as ScaffolderEntitiesProcessorModule } from '@backstage/plugin-catalog-backend-module-scaffolder-entity-model'; - -/** - * @public - * @deprecated Import from {@link @backstage/plugin-scaffolder-node#ActionContext} instead - */ -export type ActionContext = - ActionContextNode; - -/** - * @public - * @deprecated Use `createTemplateAction` from `@backstage/plugin-scaffolder-node` instead - */ -export const createTemplateAction = createTemplateActionNode; - -/** - * @public - * @deprecated Use `TaskSecrets` from `@backstage/plugin-scaffolder-node` instead - */ -export type TaskSecrets = TaskSecretsNode; - -/** - * @public - * @deprecated Use `TemplateAction` from `@backstage/plugin-scaffolder-node` instead - */ -export type TemplateAction = - TemplateActionNode; - -/** - * Options for {@link executeShellCommand}. - * - * @public - * @deprecated Use `ExecuteShellCommandOptions` from `@backstage/plugin-scaffolder-node` instead - */ -export type RunCommandOptions = ExecuteShellCommandOptionsNode; - -/** - * Run a command in a sub-process, normally a shell command. - * - * @public - * @deprecated Use `executeShellCommand` from `@backstage/plugin-scaffolder-node` instead - */ -export const executeShellCommand = executeShellCommandNode; - -/** - * A helper function that reads the contents of a directory from the given URL. - * Can be used in your own actions, and also used behind fetch:template and fetch:plain - * - * @public - * @deprecated Use `fetchContents` from `@backstage/plugin-scaffolder-node` instead - */ -export const fetchContents = fetchContentsNode; - -/** - * Adds support for scaffolder specific entity kinds to the catalog. - * - * @public - * @deprecated Import from `@backstage/plugin-catalog-backend-module-scaffolder-entity-model` instead - */ -export const ScaffolderEntitiesProcessor = ScaffolderEntitiesProcessorModule; diff --git a/plugins/scaffolder-backend/src/index.ts b/plugins/scaffolder-backend/src/index.ts index dddd42d83f..4273ed8197 100644 --- a/plugins/scaffolder-backend/src/index.ts +++ b/plugins/scaffolder-backend/src/index.ts @@ -22,7 +22,7 @@ export { scaffolderPlugin as default } from './ScaffolderPlugin'; export * from './scaffolder'; -export * from './service/router'; -export * from './lib'; - -export * from './deprecated'; +export { + type TemplatePermissionRuleInput, + type ActionPermissionRuleInput, +} from './service/permissions'; diff --git a/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts b/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts index fea04c0c1c..709cb1308d 100644 --- a/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts +++ b/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts @@ -17,8 +17,8 @@ import { Isolate } from 'isolated-vm'; import { resolvePackagePath } from '@backstage/backend-plugin-api'; import { - TemplateFilter as _TemplateFilter, - TemplateGlobal as _TemplateGlobal, + TemplateFilter, + TemplateGlobal, } from '@backstage/plugin-scaffolder-node'; import fs from 'fs-extra'; import { JsonValue } from '@backstage/types'; @@ -96,18 +96,6 @@ const { render, renderCompat } = (() => { })(); `; -/** - * @public - * @deprecated Import from `@backstage/plugin-scaffolder-node` instead. - */ -export type TemplateFilter = _TemplateFilter; - -/** - * @public - * @deprecated Import from `@backstage/plugin-scaffolder-node` instead. - */ -export type TemplateGlobal = _TemplateGlobal; - interface SecureTemplaterOptions { /* Enables jinja compatibility and the "jsonify" filter */ cookiecutterCompat?: boolean; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts b/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts index 11d6234083..c819d4c21f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts @@ -19,6 +19,7 @@ import { TemplateAction } from '@backstage/plugin-scaffolder-node'; /** * Registry of all registered template actions. * @public + * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. */ export class TemplateActionRegistry { private readonly actions = new Map(); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.examples.test.ts index 4854e39f3b..b10670d8a8 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.examples.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.examples.test.ts @@ -19,7 +19,7 @@ import { Entity } from '@backstage/catalog-model'; import { createFetchCatalogEntityAction } from './fetch'; import { examples } from './fetch.examples'; import yaml from 'yaml'; -import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; +import { mockCredentials } from '@backstage/backend-test-utils'; import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; describe('catalog:fetch examples', () => { @@ -31,27 +31,19 @@ describe('catalog:fetch examples', () => { }, } as Entity; - const catalogClient = catalogServiceMock({ entities: [entity] }); + const catalogMock = catalogServiceMock({ entities: [entity] }); const action = createFetchCatalogEntityAction({ - catalogClient, - auth: mockServices.auth(), + catalog: catalogMock, }); const credentials = mockCredentials.user(); - const token = mockCredentials.service.token({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); - - const mockContext = createMockActionContext({ - secrets: { backstageToken: token }, - }); + const mockContext = createMockActionContext(); beforeEach(() => { jest.resetAllMocks(); - jest.spyOn(catalogClient, 'getEntityByRef'); + jest.spyOn(catalogMock, 'getEntityByRef'); }); describe('fetch single entity', () => { @@ -61,9 +53,9 @@ describe('catalog:fetch examples', () => { input: yaml.parse(examples[0].example).steps[0].input, }); - expect(catalogClient.getEntityByRef).toHaveBeenCalledWith( + expect(catalogMock.getEntityByRef).toHaveBeenCalledWith( 'component:default/name', - { token }, + { credentials }, ); expect(mockContext.output).toHaveBeenCalledWith('entity', entity); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.test.ts index e049c7522c..0bea408355 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.test.ts @@ -17,7 +17,7 @@ import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { Entity } from '@backstage/catalog-model'; import { createFetchCatalogEntityAction } from './fetch'; -import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; +import { mockCredentials } from '@backstage/backend-test-utils'; import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; describe('catalog:fetch', () => { @@ -31,14 +31,7 @@ describe('catalog:fetch', () => { const credentials = mockCredentials.user(); - const token = mockCredentials.service.token({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); - - const mockContext = createMockActionContext({ - secrets: { backstageToken: token }, - }); + const mockContext = createMockActionContext(); beforeEach(() => { jest.resetAllMocks(); @@ -46,11 +39,11 @@ describe('catalog:fetch', () => { describe('fetch single entity', () => { it('should return entity from catalog', async () => { - const catalogClient = catalogServiceMock({ entities: [component] }); - jest.spyOn(catalogClient, 'getEntityByRef'); + const catalogMock = catalogServiceMock({ entities: [component] }); + jest.spyOn(catalogMock, 'getEntityByRef'); + const action = createFetchCatalogEntityAction({ - catalogClient, - auth: mockServices.auth(), + catalog: catalogMock, }); await action.handler({ @@ -60,21 +53,20 @@ describe('catalog:fetch', () => { }, }); - expect(catalogClient.getEntityByRef).toHaveBeenCalledWith( + expect(catalogMock.getEntityByRef).toHaveBeenCalledWith( 'component:default/test', - { token }, + { credentials }, ); expect(mockContext.output).toHaveBeenCalledWith('entity', component); }); it('should throw error if entity fetch fails from catalog and optional is false', async () => { - const catalogClient = catalogServiceMock.mock({ + const catalogMock = catalogServiceMock.mock({ getEntityByRef: () => Promise.reject(new Error('Not found')), }); - jest.spyOn(catalogClient, 'getEntityByRef'); + const action = createFetchCatalogEntityAction({ - catalogClient, - auth: mockServices.auth(), + catalog: catalogMock, }); await expect( @@ -86,19 +78,19 @@ describe('catalog:fetch', () => { }), ).rejects.toThrow('Not found'); - expect(catalogClient.getEntityByRef).toHaveBeenCalledWith( + expect(catalogMock.getEntityByRef).toHaveBeenCalledWith( 'component:default/test', - { token }, + { credentials }, ); expect(mockContext.output).not.toHaveBeenCalled(); }); it('should throw error if entity not in catalog and optional is false', async () => { - const catalogClient = catalogServiceMock({ entities: [] }); - jest.spyOn(catalogClient, 'getEntityByRef'); + const catalogMock = catalogServiceMock({ entities: [] }); + jest.spyOn(catalogMock, 'getEntityByRef'); + const action = createFetchCatalogEntityAction({ - catalogClient, - auth: mockServices.auth(), + catalog: catalogMock, }); await expect( @@ -110,9 +102,9 @@ describe('catalog:fetch', () => { }), ).rejects.toThrow('Entity component:default/test not found'); - expect(catalogClient.getEntityByRef).toHaveBeenCalledWith( + expect(catalogMock.getEntityByRef).toHaveBeenCalledWith( 'component:default/test', - { token }, + { credentials }, ); expect(mockContext.output).not.toHaveBeenCalled(); }); @@ -125,11 +117,12 @@ describe('catalog:fetch', () => { namespace: 'ns', }, } as Entity; - const catalogClient = catalogServiceMock({ entities: [entity] }); - jest.spyOn(catalogClient, 'getEntityByRef'); + + const catalogMock = catalogServiceMock({ entities: [entity] }); + jest.spyOn(catalogMock, 'getEntityByRef'); + const action = createFetchCatalogEntityAction({ - catalogClient, - auth: mockServices.auth(), + catalog: catalogMock, }); await action.handler({ @@ -141,21 +134,21 @@ describe('catalog:fetch', () => { }, }); - expect(catalogClient.getEntityByRef).toHaveBeenCalledWith( - 'group:ns/test', - { token }, - ); + expect(catalogMock.getEntityByRef).toHaveBeenCalledWith('group:ns/test', { + credentials, + }); + expect(mockContext.output).toHaveBeenCalledWith('entity', entity); }); }); describe('fetch multiple entities', () => { it('should return entities from catalog', async () => { - const catalogClient = catalogServiceMock({ entities: [component] }); - jest.spyOn(catalogClient, 'getEntitiesByRefs'); + const catalogMock = catalogServiceMock({ entities: [component] }); + jest.spyOn(catalogMock, 'getEntitiesByRefs'); + const action = createFetchCatalogEntityAction({ - catalogClient, - auth: mockServices.auth(), + catalog: catalogMock, }); await action.handler({ @@ -165,19 +158,18 @@ describe('catalog:fetch', () => { }, }); - expect(catalogClient.getEntitiesByRefs).toHaveBeenCalledWith( + expect(catalogMock.getEntitiesByRefs).toHaveBeenCalledWith( { entityRefs: ['component:default/test'] }, - { token }, + { credentials }, ); expect(mockContext.output).toHaveBeenCalledWith('entities', [component]); }); it('should throw error if undefined is returned for some entity', async () => { - const catalogClient = catalogServiceMock({ entities: [component] }); - jest.spyOn(catalogClient, 'getEntitiesByRefs'); + const catalogMock = catalogServiceMock({ entities: [component] }); + jest.spyOn(catalogMock, 'getEntitiesByRefs'); const action = createFetchCatalogEntityAction({ - catalogClient, - auth: mockServices.auth(), + catalog: catalogMock, }); await expect( @@ -190,21 +182,21 @@ describe('catalog:fetch', () => { }), ).rejects.toThrow('Entity component:default/test2 not found'); - expect(catalogClient.getEntitiesByRefs).toHaveBeenCalledWith( + expect(catalogMock.getEntitiesByRefs).toHaveBeenCalledWith( { entityRefs: ['component:default/test', 'component:default/test2'], }, - { token }, + { credentials }, ); expect(mockContext.output).not.toHaveBeenCalled(); }); it('should return null in case some of the entities not found and optional is true', async () => { - const catalogClient = catalogServiceMock({ entities: [component] }); - jest.spyOn(catalogClient, 'getEntitiesByRefs'); + const catalogMock = catalogServiceMock({ entities: [component] }); + jest.spyOn(catalogMock, 'getEntitiesByRefs'); + const action = createFetchCatalogEntityAction({ - catalogClient, - auth: mockServices.auth(), + catalog: catalogMock, }); await action.handler({ @@ -215,9 +207,9 @@ describe('catalog:fetch', () => { }, }); - expect(catalogClient.getEntitiesByRefs).toHaveBeenCalledWith( + expect(catalogMock.getEntitiesByRefs).toHaveBeenCalledWith( { entityRefs: ['component:default/test', 'component:default/test2'] }, - { token }, + { credentials }, ); expect(mockContext.output).toHaveBeenCalledWith('entities', [ component, @@ -240,13 +232,14 @@ describe('catalog:fetch', () => { }, kind: 'User', } as Entity; - const catalogClient = catalogServiceMock({ + + const catalogMock = catalogServiceMock({ entities: [entity1, entity2], }); - jest.spyOn(catalogClient, 'getEntitiesByRefs'); + jest.spyOn(catalogMock, 'getEntitiesByRefs'); + const action = createFetchCatalogEntityAction({ - catalogClient, - auth: mockServices.auth(), + catalog: catalogMock, }); await action.handler({ @@ -258,9 +251,9 @@ describe('catalog:fetch', () => { }, }); - expect(catalogClient.getEntitiesByRefs).toHaveBeenCalledWith( + expect(catalogMock.getEntitiesByRefs).toHaveBeenCalledWith( { entityRefs: ['group:ns/test', 'user:default/test'] }, - { token }, + { credentials }, ); expect(mockContext.output).toHaveBeenCalledWith('entities', [ diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.ts index e21930ed78..e73c882a9a 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.ts @@ -14,11 +14,10 @@ * limitations under the License. */ -import { CatalogApi } from '@backstage/catalog-client'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { parseEntityRef, stringifyEntityRef } from '@backstage/catalog-model'; import { examples } from './fetch.examples'; -import { AuthService } from '@backstage/backend-plugin-api'; +import { CatalogService } from '@backstage/plugin-catalog-node'; const id = 'catalog:fetch'; @@ -28,10 +27,9 @@ const id = 'catalog:fetch'; * @public */ export function createFetchCatalogEntityAction(options: { - catalogClient: CatalogApi; - auth?: AuthService; + catalog: CatalogService; }) { - const { catalogClient, auth } = options; + const { catalog } = options; return createTemplateAction({ id, @@ -95,18 +93,13 @@ export function createFetchCatalogEntityAction(options: { throw new Error('Missing entity reference or references'); } - const { token } = (await auth?.getPluginRequestToken({ - onBehalfOf: await ctx.getInitiatorCredentials(), - targetPluginId: 'catalog', - })) ?? { token: ctx.secrets?.backstageToken }; - if (entityRef) { - const entity = await catalogClient.getEntityByRef( + const entity = await catalog.getEntityByRef( stringifyEntityRef( parseEntityRef(entityRef, { defaultKind, defaultNamespace }), ), { - token, + credentials: await ctx.getInitiatorCredentials(), }, ); @@ -117,7 +110,7 @@ export function createFetchCatalogEntityAction(options: { } if (entityRefs) { - const entities = await catalogClient.getEntitiesByRefs( + const entities = await catalog.getEntitiesByRefs( { entityRefs: entityRefs.map(ref => stringifyEntityRef( @@ -125,9 +118,7 @@ export function createFetchCatalogEntityAction(options: { ), ), }, - { - token, - }, + { credentials: await ctx.getInitiatorCredentials() }, ); const finalEntities = entities.items.map((e, i) => { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.examples.test.ts index 27690b0df3..b0f17d98c3 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.examples.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.examples.test.ts @@ -21,7 +21,7 @@ import { createCatalogRegisterAction } from './register'; import { Entity } from '@backstage/catalog-model'; import { examples } from './register.examples'; import yaml from 'yaml'; -import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; +import { mockCredentials } from '@backstage/backend-test-utils'; import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; describe('catalog:register', () => { @@ -33,28 +33,22 @@ describe('catalog:register', () => { }), ); - const catalogClient = catalogServiceMock.mock(); + const catalogMock = catalogServiceMock.mock(); const action = createCatalogRegisterAction({ integrations, - catalogClient, - auth: mockServices.auth(), + catalog: catalogMock, }); const credentials = mockCredentials.user(); - const token = mockCredentials.service.token({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); - const mockContext = createMockActionContext(); beforeEach(() => { jest.resetAllMocks(); }); it('should register location in catalog', async () => { - catalogClient.addLocation + catalogMock.addLocation .mockResolvedValueOnce({ location: null as any, entities: [], @@ -71,21 +65,22 @@ describe('catalog:register', () => { } as Entity, ], }); + await action.handler({ ...mockContext, input: yaml.parse(examples[0].example).steps[0].input, }); - expect(catalogClient.addLocation).toHaveBeenNthCalledWith( + expect(catalogMock.addLocation).toHaveBeenNthCalledWith( 1, { type: 'url', target: 'http://github.com/backstage/backstage/blob/master/catalog-info.yaml', }, - { token }, + { credentials }, ); - expect(catalogClient.addLocation).toHaveBeenNthCalledWith( + expect(catalogMock.addLocation).toHaveBeenNthCalledWith( 2, { dryRun: true, @@ -93,7 +88,7 @@ describe('catalog:register', () => { target: 'http://github.com/backstage/backstage/blob/master/catalog-info.yaml', }, - { token }, + { credentials }, ); expect(mockContext.output).toHaveBeenCalledWith( diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.test.ts index 839ec50886..ad84624129 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.test.ts @@ -19,7 +19,7 @@ import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import { createCatalogRegisterAction } from './register'; import { Entity } from '@backstage/catalog-model'; -import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; +import { mockCredentials } from '@backstage/backend-test-utils'; import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; describe('catalog:register', () => { @@ -31,21 +31,15 @@ describe('catalog:register', () => { }), ); - const catalogClient = catalogServiceMock.mock(); + const catalogMock = catalogServiceMock.mock(); const action = createCatalogRegisterAction({ integrations, - catalogClient, - auth: mockServices.auth(), + catalog: catalogMock, }); const credentials = mockCredentials.user(); - const token = mockCredentials.service.token({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); - const mockContext = createMockActionContext(); beforeEach(() => { @@ -66,7 +60,7 @@ describe('catalog:register', () => { }); it('should register location in catalog', async () => { - catalogClient.addLocation + catalogMock.addLocation .mockResolvedValueOnce({ location: null as any, entities: [], @@ -90,22 +84,22 @@ describe('catalog:register', () => { }, }); - expect(catalogClient.addLocation).toHaveBeenNthCalledWith( + expect(catalogMock.addLocation).toHaveBeenNthCalledWith( 1, { type: 'url', target: 'http://foo/var', }, - { token }, + { credentials }, ); - expect(catalogClient.addLocation).toHaveBeenNthCalledWith( + expect(catalogMock.addLocation).toHaveBeenNthCalledWith( 2, { dryRun: true, type: 'url', target: 'http://foo/var', }, - { token }, + { credentials }, ); expect(mockContext.output).toHaveBeenCalledWith( @@ -119,7 +113,7 @@ describe('catalog:register', () => { }); it('should return entityRef with the Component entity and not the generated location', async () => { - catalogClient.addLocation + catalogMock.addLocation .mockResolvedValueOnce({ location: null as any, entities: [], @@ -170,7 +164,7 @@ describe('catalog:register', () => { }); it('should return entityRef with the next non-generated entity if no Component kind can be found', async () => { - catalogClient.addLocation + catalogMock.addLocation .mockResolvedValueOnce({ location: null as any, entities: [], @@ -214,7 +208,7 @@ describe('catalog:register', () => { }); it('should return entityRef with the first entity if no non-generated entities can be found', async () => { - catalogClient.addLocation + catalogMock.addLocation .mockResolvedValueOnce({ location: null as any, entities: [], @@ -251,7 +245,7 @@ describe('catalog:register', () => { }); it('should not return entityRef if there are no entities', async () => { - catalogClient.addLocation + catalogMock.addLocation .mockResolvedValueOnce({ location: null as any, entities: [], @@ -273,7 +267,7 @@ describe('catalog:register', () => { }); it('should ignore failures when dry running the location in the catalog if `optional` is set', async () => { - catalogClient.addLocation + catalogMock.addLocation .mockRejectedValueOnce(new Error('Not found')) .mockRejectedValueOnce(new Error('Not found')); await action.handler({ @@ -284,22 +278,22 @@ describe('catalog:register', () => { }, }); - expect(catalogClient.addLocation).toHaveBeenNthCalledWith( + expect(catalogMock.addLocation).toHaveBeenNthCalledWith( 1, { type: 'url', target: 'http://foo/var', }, - { token }, + { credentials }, ); - expect(catalogClient.addLocation).toHaveBeenNthCalledWith( + expect(catalogMock.addLocation).toHaveBeenNthCalledWith( 2, { dryRun: true, type: 'url', target: 'http://foo/var', }, - { token }, + { credentials }, ); expect(mockContext.output).toHaveBeenCalledWith( @@ -309,7 +303,7 @@ describe('catalog:register', () => { }); it('should fetch entities when adding location in the catalog fails and `optional` is set', async () => { - catalogClient.addLocation + catalogMock.addLocation .mockRejectedValueOnce(new Error('Already registered')) .mockResolvedValueOnce({ location: null as any, @@ -331,22 +325,22 @@ describe('catalog:register', () => { }, }); - expect(catalogClient.addLocation).toHaveBeenNthCalledWith( + expect(catalogMock.addLocation).toHaveBeenNthCalledWith( 1, { type: 'url', target: 'http://foo/var', }, - { token }, + { credentials }, ); - expect(catalogClient.addLocation).toHaveBeenNthCalledWith( + expect(catalogMock.addLocation).toHaveBeenNthCalledWith( 2, { dryRun: true, type: 'url', target: 'http://foo/var', }, - { token }, + { credentials }, ); expect(mockContext.output).toHaveBeenCalledWith( diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.ts index eab31f14df..65102d8375 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.ts @@ -16,11 +16,10 @@ import { InputError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; -import { CatalogApi } from '@backstage/catalog-client'; import { stringifyEntityRef, Entity } from '@backstage/catalog-model'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { examples } from './register.examples'; -import { AuthService } from '@backstage/backend-plugin-api'; +import { CatalogService } from '@backstage/plugin-catalog-node'; const id = 'catalog:register'; @@ -29,79 +28,50 @@ const id = 'catalog:register'; * @public */ export function createCatalogRegisterAction(options: { - catalogClient: CatalogApi; + catalog: CatalogService; integrations: ScmIntegrations; - auth?: AuthService; }) { - const { catalogClient, integrations, auth } = options; + const { catalog, integrations } = options; - return createTemplateAction< - | { catalogInfoUrl: string; optional?: boolean } - | { repoContentsUrl: string; catalogInfoPath?: string; optional?: boolean } - >({ + return createTemplateAction({ id, description: 'Registers entities from a catalog descriptor file in the workspace into the software catalog.', examples, schema: { - input: { - oneOf: [ - { - type: 'object', - required: ['catalogInfoUrl'], - properties: { - catalogInfoUrl: { - title: 'Catalog Info URL', - description: - 'An absolute URL pointing to the catalog info file location', - type: 'string', - }, - optional: { - title: 'Optional', + input: z => + z.union([ + z.object({ + catalogInfoUrl: z.string({ + description: + 'An absolute URL pointing to the catalog info file location', + }), + optional: z + .boolean({ description: 'Permit the registered location to optionally exist. Default: false', - type: 'boolean', - }, - }, - }, - { - type: 'object', - required: ['repoContentsUrl'], - properties: { - repoContentsUrl: { - title: 'Repository Contents URL', - description: - 'An absolute URL pointing to the root of a repository directory tree', - type: 'string', - }, - catalogInfoPath: { - title: 'Fetch URL', + }) + .optional(), + }), + z.object({ + repoContentsUrl: z.string({ + description: + 'An absolute URL pointing to the root of a repository directory tree', + }), + catalogInfoPath: z + .string({ description: 'A relative path from the repo root pointing to the catalog info file, defaults to /catalog-info.yaml', - type: 'string', - }, - optional: { - title: 'Optional', + }) + .optional(), + optional: z + .boolean({ description: 'Permit the registered location to optionally exist. Default: false', - type: 'boolean', - }, - }, - }, - ], - }, - output: { - type: 'object', - required: ['catalogInfoUrl'], - properties: { - entityRef: { - type: 'string', - }, - catalogInfoUrl: { - type: 'string', - }, - }, - }, + }) + .optional(), + }), + ]), }, async handler(ctx) { const { input } = ctx; @@ -127,19 +97,14 @@ export function createCatalogRegisterAction(options: { ctx.logger.info(`Registering ${catalogInfoUrl} in the catalog`); - const { token } = (await auth?.getPluginRequestToken({ - onBehalfOf: await ctx.getInitiatorCredentials(), - targetPluginId: 'catalog', - })) ?? { token: ctx.secrets?.backstageToken }; - try { // 1st try to register the location, this will throw an error if the location already exists (see catch) - await catalogClient.addLocation( + await catalog.addLocation( { type: 'url', target: catalogInfoUrl, }, - token ? { token } : {}, + { credentials: await ctx.getInitiatorCredentials() }, ); } catch (e) { if (!input.optional) { @@ -150,13 +115,13 @@ export function createCatalogRegisterAction(options: { try { // 2nd retry the registration as a dry run, this will not throw an error if the location already exists - const result = await catalogClient.addLocation( + const result = await catalog.addLocation( { dryRun: true, type: 'url', target: catalogInfoUrl, }, - token ? { token } : {}, + { credentials: await ctx.getInitiatorCredentials() }, ); if (result.entities.length) { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts index 29ce6c986d..e01cc3b88a 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts @@ -18,7 +18,6 @@ import fs from 'fs-extra'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import * as yaml from 'yaml'; import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; -import { z } from 'zod'; import { examples } from './write.examples'; const id = 'catalog:write'; @@ -33,18 +32,17 @@ export function createCatalogWriteAction() { id, description: 'Writes the catalog-info.yaml for your template', schema: { - input: z.object({ - filePath: z - .string() - .optional() - .describe('Defaults to catalog-info.yaml'), + input: { + filePath: z => + z.string().optional().describe('Defaults to catalog-info.yaml'), // TODO: this should reference an zod entity validator if it existed. - entity: z - .record(z.any()) - .describe( - 'You can provide the same values used in the Entity schema.', - ), - }), + entity: z => + z + .record(z.any()) + .describe( + 'You can provide the same values used in the Entity schema.', + ), + }, }, examples, supportsDryRun: true, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts deleted file mode 100644 index d2a03412cc..0000000000 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts +++ /dev/null @@ -1,268 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { CatalogApi } from '@backstage/catalog-client'; -import { Config } from '@backstage/config'; -import { - DefaultGithubCredentialsProvider, - GithubCredentialsProvider, - ScmIntegrations, -} from '@backstage/integration'; -import { - TemplateAction, - TemplateFilter, - TemplateGlobal, -} from '@backstage/plugin-scaffolder-node'; -import { - createCatalogRegisterAction, - createCatalogWriteAction, - createFetchCatalogEntityAction, -} from './catalog'; - -import { createDebugLogAction, createWaitAction } from './debug'; -import { - createFetchPlainAction, - createFetchPlainFileAction, - createFetchTemplateAction, - createFetchTemplateFileAction, -} from './fetch'; -import { - createFilesystemDeleteAction, - createFilesystemReadDirAction, - createFilesystemRenameAction, -} from './filesystem'; -import { - createGithubActionsDispatchAction, - createGithubAutolinksAction, - createGithubDeployKeyAction, - createGithubEnvironmentAction, - createGithubIssuesLabelAction, - createGithubRepoCreateAction, - createGithubRepoPushAction, - createGithubWebhookAction, - createPublishGithubAction, - createPublishGithubPullRequestAction, -} from '@backstage/plugin-scaffolder-backend-module-github'; - -import { createPublishAzureAction } from '@backstage/plugin-scaffolder-backend-module-azure'; - -import { createPublishBitbucketAction } from '@backstage/plugin-scaffolder-backend-module-bitbucket'; - -import { - createPublishBitbucketCloudAction, - createBitbucketPipelinesRunAction, - createPublishBitbucketCloudPullRequestAction, -} from '@backstage/plugin-scaffolder-backend-module-bitbucket-cloud'; - -import { - createPublishBitbucketServerAction, - createPublishBitbucketServerPullRequestAction, -} from '@backstage/plugin-scaffolder-backend-module-bitbucket-server'; - -import { - createPublishGerritAction, - createPublishGerritReviewAction, -} from '@backstage/plugin-scaffolder-backend-module-gerrit'; - -import { - createPublishGitlabAction, - createGitlabRepoPushAction, - createPublishGitlabMergeRequestAction, -} from '@backstage/plugin-scaffolder-backend-module-gitlab'; - -import { createPublishGiteaAction } from '@backstage/plugin-scaffolder-backend-module-gitea'; -import { AuthService, UrlReaderService } from '@backstage/backend-plugin-api'; - -/** - * The options passed to {@link createBuiltinActions} - * @public - */ -export interface CreateBuiltInActionsOptions { - /** - * The {@link @backstage/backend-plugin-api#UrlReaderService} interface that will be used in the default actions. - */ - reader: UrlReaderService; - /** - * The {@link @backstage/integrations#ScmIntegrations} that will be used in the default actions. - */ - integrations: ScmIntegrations; - /** - * The {@link @backstage/catalog-client#CatalogApi} that will be used in the default actions. - */ - catalogClient: CatalogApi; - /** - * The {@link @backstage/backend-plugin-api#AuthService} that will be used in the default actions. - */ - auth?: AuthService; - /** - * The {@link @backstage/config#Config} that will be used in the default actions. - */ - config: Config; - /** - * Additional custom filters that will be passed to the nunjucks template engine for use in - * Template Manifests and also template skeleton files when using `fetch:template`. - */ - additionalTemplateFilters?: Record; - additionalTemplateGlobals?: Record; -} - -/** - * A function to generate create a list of default actions that the scaffolder provides. - * Is called internally in the default setup, but can be used when adding your own actions or overriding the default ones - * - * TODO(blam): version 2 of the scaffolder shouldn't ship with the additional modules. We should ship the basics, and let people install - * modules for the providers they want to use. - * @public - * @returns A list of actions that can be used in the scaffolder - * - */ -export const createBuiltinActions = ( - options: CreateBuiltInActionsOptions, -): TemplateAction[] => { - const { - reader, - integrations, - catalogClient, - auth, - config, - additionalTemplateFilters, - additionalTemplateGlobals, - } = options; - - const githubCredentialsProvider: GithubCredentialsProvider = - DefaultGithubCredentialsProvider.fromIntegrations(integrations); - - const actions = [ - createFetchPlainAction({ - reader, - integrations, - }), - createFetchPlainFileAction({ - reader, - integrations, - }), - createFetchTemplateAction({ - integrations, - reader, - additionalTemplateFilters, - additionalTemplateGlobals, - }), - createFetchTemplateFileAction({ - integrations, - reader, - additionalTemplateFilters, - additionalTemplateGlobals, - }), - createPublishGerritAction({ - integrations, - config, - }), - createPublishGerritReviewAction({ - integrations, - config, - }), - createPublishGiteaAction({ - integrations, - config, - }), - createPublishGithubAction({ - integrations, - config, - githubCredentialsProvider, - }), - createPublishGithubPullRequestAction({ - integrations, - githubCredentialsProvider, - config, - }), - createPublishGitlabAction({ - integrations, - config, - }), - createPublishGitlabMergeRequestAction({ - integrations, - }), - createGitlabRepoPushAction({ - integrations, - }), - createPublishBitbucketAction({ - integrations, - config, - }), - createPublishBitbucketCloudAction({ - integrations, - config, - }), - createPublishBitbucketCloudPullRequestAction({ integrations, config }), - createPublishBitbucketServerAction({ - integrations, - config, - }), - createPublishBitbucketServerPullRequestAction({ - integrations, - config, - }), - createPublishAzureAction({ - integrations, - config, - }), - createDebugLogAction(), - createWaitAction(), - createCatalogRegisterAction({ catalogClient, integrations, auth }), - createFetchCatalogEntityAction({ catalogClient, auth }), - createCatalogWriteAction(), - createFilesystemDeleteAction(), - createFilesystemReadDirAction(), - createFilesystemRenameAction(), - createGithubActionsDispatchAction({ - integrations, - githubCredentialsProvider, - }), - createGithubWebhookAction({ - integrations, - githubCredentialsProvider, - }), - createGithubIssuesLabelAction({ - integrations, - githubCredentialsProvider, - }), - createGithubRepoCreateAction({ - integrations, - githubCredentialsProvider, - }), - createGithubRepoPushAction({ - integrations, - config, - githubCredentialsProvider, - }), - createGithubEnvironmentAction({ - integrations, - catalogClient, - }), - createGithubDeployKeyAction({ - integrations, - }), - createGithubAutolinksAction({ - integrations, - githubCredentialsProvider, - }), - createBitbucketPipelinesRunAction({ - integrations, - }), - ]; - - return actions as TemplateAction[]; -}; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.ts index 2ff64e1025..6eae1794f8 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.ts @@ -19,7 +19,6 @@ import { join, relative } from 'path'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { examples } from './log.examples'; import fs from 'fs'; -import { z } from 'zod'; const id = 'debug:log'; @@ -34,24 +33,23 @@ const id = 'debug:log'; * @public */ export function createDebugLogAction() { - return createTemplateAction<{ - message?: string; - listWorkspace?: boolean | 'with-filenames' | 'with-contents'; - }>({ + return createTemplateAction({ id, description: 'Writes a message into the log and/or lists all files in the workspace.', examples, schema: { - input: z.object({ - message: z.string({ description: 'Message to output.' }).optional(), - listWorkspace: z - .union([z.boolean(), z.enum(['with-filenames', 'with-contents'])], { - description: - 'List all files in the workspace. If used with "with-contents", also the file contents are listed.', - }) - .optional(), - }), + input: { + message: z => + z.string({ description: 'Message to output.' }).optional(), + listWorkspace: z => + z + .union([z.boolean(), z.enum(['with-filenames', 'with-contents'])], { + description: + 'List all files in the workspace. If used with "with-contents", also the file contents are listed.', + }) + .optional(), + }, }, supportsDryRun: true, async handler(ctx) { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.examples.test.ts index 8bc29a74c9..6ffd338a11 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.examples.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.examples.test.ts @@ -15,7 +15,6 @@ */ import { createWaitAction } from './wait'; -import { Writable } from 'stream'; import { examples } from './wait.examples'; import yaml from 'yaml'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; @@ -23,13 +22,7 @@ import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test- describe('debug:wait examples', () => { const action = createWaitAction(); - const logStream = { - write: jest.fn(), - } as jest.Mocked> as jest.Mocked; - - const mockContext = createMockActionContext({ - logStream, - }); + const mockContext = createMockActionContext(); beforeEach(() => { jest.resetAllMocks(); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.test.ts index fc688eee44..919cb9faee 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.test.ts @@ -15,19 +15,12 @@ */ import { createWaitAction } from './wait'; -import { Writable } from 'stream'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; describe('debug:wait', () => { const action = createWaitAction(); - const logStream = { - write: jest.fn(), - } as jest.Mocked> as jest.Mocked; - - const mockContext = createMockActionContext({ - logStream, - }); + const mockContext = createMockActionContext(); beforeEach(() => { jest.resetAllMocks(); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.ts index fd4a77754b..e048720f29 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.ts @@ -48,27 +48,30 @@ export function createWaitAction(options?: { return Duration.fromISOTime(MAX_WAIT_TIME_IN_ISO); }; - return createTemplateAction({ + return createTemplateAction({ id, description: 'Waits for a certain period of time.', examples, schema: { input: { - type: 'object', - properties: { - minutes: { - title: 'Waiting period in minutes.', - type: 'number', - }, - seconds: { - title: 'Waiting period in seconds.', - type: 'number', - }, - milliseconds: { - title: 'Waiting period in milliseconds.', - type: 'number', - }, - }, + minutes: z => + z + .number({ + description: 'Waiting period in minutes.', + }) + .optional(), + seconds: z => + z + .number({ + description: 'Waiting period in seconds.', + }) + .optional(), + milliseconds: z => + z + .number({ + description: 'Waiting period in milliseconds.', + }) + .optional(), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts index a4f795dd54..c0bed9bea7 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts @@ -39,39 +39,32 @@ export function createFetchPlainAction(options: { }) { const { reader, integrations } = options; - return createTemplateAction<{ - url: string; - targetPath?: string; - token?: string; - }>({ + return createTemplateAction({ id: ACTION_ID, examples, description: 'Downloads content and places it in the workspace, or optionally in a subdirectory specified by the `targetPath` input option.', schema: { input: { - type: 'object', - required: ['url'], - properties: { - url: { - title: 'Fetch URL', + url: z => + z.string({ description: 'Relative path or absolute URL pointing to the directory tree to fetch', - type: 'string', - }, - targetPath: { - title: 'Target Path', - description: - 'Target path within the working directory to download the contents to.', - type: 'string', - }, - token: { - title: 'Token', - description: - 'An optional token to use for authentication when reading the resources.', - type: 'string', - }, - }, + }), + targetPath: z => + z + .string({ + description: + 'Target path within the working directory to download the contents to.', + }) + .optional(), + token: z => + z + .string({ + description: + 'An optional token to use for authentication when reading the resources.', + }) + .optional(), }, }, supportsDryRun: true, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.ts index a07c9bfc6b..c132214c10 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.ts @@ -14,18 +14,20 @@ * limitations under the License. */ -import { UrlReaderService } from '@backstage/backend-plugin-api'; -import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; +import { + resolveSafeChildPath, + UrlReaderService, +} from '@backstage/backend-plugin-api'; import { ScmIntegrations } from '@backstage/integration'; import { examples } from './plainFile.examples'; + import { createTemplateAction, fetchFile, } from '@backstage/plugin-scaffolder-node'; /** - * Downloads content and places it in the workspace, or optionally - * in a subdirectory specified by the 'targetPath' input option. + * Downloads a single file and places it in the workspace. * @public */ export function createFetchPlainFileAction(options: { @@ -34,38 +36,29 @@ export function createFetchPlainFileAction(options: { }) { const { reader, integrations } = options; - return createTemplateAction<{ - url: string; - targetPath: string; - token?: string; - }>({ + return createTemplateAction({ id: 'fetch:plain:file', description: 'Downloads single file and places it in the workspace.', examples, schema: { input: { - type: 'object', - required: ['url', 'targetPath'], - properties: { - url: { - title: 'Fetch URL', + url: z => + z.string({ description: 'Relative path or absolute URL pointing to the single file to fetch.', - type: 'string', - }, - targetPath: { - title: 'Target Path', + }), + targetPath: z => + z.string({ description: 'Target path within the working directory to download the file as.', - type: 'string', - }, - token: { - title: 'Token', - description: - 'An optional token to use for authentication when reading the resources.', - type: 'string', - }, - }, + }), + token: z => + z + .string({ + description: + 'An optional token to use for authentication when reading the resources.', + }) + .optional(), }, }, supportsDryRun: true, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts index 61918e8450..6494e562d2 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts @@ -98,42 +98,6 @@ describe('fetch:template', () => { ); }); - it('throws if copyWithoutRender parameter is not an array', async () => { - await expect(() => - action.handler( - mockContext({ copyWithoutRender: 'abc' as unknown as string[] }), - ), - ).rejects.toThrow( - /copyWithoutRender\/copyWithoutTemplating must be an array/i, - ); - }); - - it('throws if both copyWithoutRender and copyWithoutTemplating are used', async () => { - await expect(() => - action.handler( - mockContext({ - copyWithoutRender: 'abc' as unknown as string[], - copyWithoutTemplating: 'def' as unknown as string[], - }), - ), - ).rejects.toThrow( - /copyWithoutRender and copyWithoutTemplating can not be used at the same time/i, - ); - }); - - it('throws if copyWithoutRender is used with extension', async () => { - await expect(() => - action.handler( - mockContext({ - copyWithoutRender: ['abc'], - templateFileExtension: true, - }), - ), - ).rejects.toThrow( - /input extension incompatible with copyWithoutRender\/copyWithoutTemplating and cookiecutterCompat/, - ); - }); - it('throws if cookiecutterCompat is used with extension', async () => { await expect(() => action.handler( @@ -143,7 +107,7 @@ describe('fetch:template', () => { }), ), ).rejects.toThrow( - /input extension incompatible with copyWithoutRender\/copyWithoutTemplating and cookiecutterCompat/, + /input extension incompatible with copyWithoutTemplating and cookiecutterCompat/, ); }); @@ -378,55 +342,6 @@ describe('fetch:template', () => { }); }); - describe('copyWithoutRender', () => { - let context: ActionContext; - - beforeEach(async () => { - context = mockContext({ - values: { - name: 'test-project', - count: 1234, - }, - copyWithoutRender: ['.unprocessed'], - }); - - mockFetchContents.mockImplementation(({ outputPath }) => { - mockDir.setContent({ - [outputPath]: { - processed: { - 'templated-content-${{ values.name }}.txt': '${{ values.count }}', - }, - '.unprocessed': { - 'templated-content-${{ values.name }}.txt': '${{ values.count }}', - }, - }, - }); - - return Promise.resolve(); - }); - - await action.handler(context); - }); - - it('ignores template syntax in files matched in copyWithoutRender', async () => { - await expect( - fs.readFile( - `${workspacePath}/target/.unprocessed/templated-content-\${{ values.name }}.txt`, - 'utf-8', - ), - ).resolves.toEqual('${{ values.count }}'); - }); - - it('processes files not matched in copyWithoutRender', async () => { - await expect( - fs.readFile( - `${workspacePath}/target/processed/templated-content-test-project.txt`, - 'utf-8', - ), - ).resolves.toEqual('1234'); - }); - }); - describe('copyWithoutTemplating', () => { let context: ActionContext; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts index 4a80cd208d..159bd1c4a2 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts @@ -41,93 +41,87 @@ export function createFetchTemplateAction(options: { additionalTemplateFilters?: Record; additionalTemplateGlobals?: Record; }) { - return createTemplateAction<{ - url: string; - targetPath?: string; - values: any; - templateFileExtension?: string | boolean; - - // Cookiecutter compat options - /** - * @deprecated This field is deprecated in favor of copyWithoutTemplating. - */ - copyWithoutRender?: string[]; - copyWithoutTemplating?: string[]; - cookiecutterCompat?: boolean; - replace?: boolean; - trimBlocks?: boolean; - lstripBlocks?: boolean; - token?: string; - }>({ + return createTemplateAction({ id: 'fetch:template', description: 'Downloads a skeleton, templates variables into file and directory names and content, and places the result in the workspace, or optionally in a subdirectory specified by the `targetPath` input option.', examples, schema: { input: { - type: 'object', - required: ['url'], - properties: { - url: { - title: 'Fetch URL', + url: z => + z.string({ description: 'Relative path or absolute URL pointing to the directory tree to fetch', - type: 'string', - }, - targetPath: { - title: 'Target Path', - description: - 'Target path within the working directory to download the contents to. Defaults to the working directory root.', - type: 'string', - }, - values: { - title: 'Template Values', - description: 'Values to pass on to the templating engine', - type: 'object', - }, - copyWithoutRender: { - title: '[Deprecated] Copy Without Render', - description: - 'An array of glob patterns. Any files or directories which match are copied without being processed as templates.', - type: 'array', - items: { - type: 'string', - }, - }, - copyWithoutTemplating: { - title: 'Copy Without Templating', - description: - 'An array of glob patterns. Contents of matched files or directories are copied without being processed, but paths are subject to rendering.', - type: 'array', - items: { - type: 'string', - }, - }, - cookiecutterCompat: { - title: 'Cookiecutter compatibility mode', - description: - 'Enable features to maximise compatibility with templates built for fetch:cookiecutter', - type: 'boolean', - }, - templateFileExtension: { - title: 'Template File Extension', - description: - 'If set, only files with the given extension will be templated. If set to `true`, the default extension `.njk` is used.', - type: ['string', 'boolean'], - }, - replace: { - title: 'Replace files', - description: - 'If set, replace files in targetPath instead of skipping existing ones.', - type: 'boolean', - }, - token: { - title: 'Token', - description: - 'An optional token to use for authentication when reading the resources.', - type: 'string', - }, - }, + }), + targetPath: z => + z + .string({ + description: + 'Target path within the working directory to download the contents to. Defaults to the working directory root.', + }) + .optional(), + values: z => + z + .record(z.any(), { + description: 'Values to pass on to the templating engine', + }) + .optional(), + copyWithoutRender: z => + z + .array(z.string(), { + description: + 'An array of glob patterns. Any files or directories which match are copied without being processed as templates.', + }) + .optional(), + copyWithoutTemplating: z => + z + .array(z.string(), { + description: + 'An array of glob patterns. Contents of matched files or directories are copied without being processed, but paths are subject to rendering.', + }) + .optional(), + cookiecutterCompat: z => + z + .boolean({ + description: + 'Enable features to maximise compatibility with templates built for fetch:cookiecutter', + }) + .optional(), + templateFileExtension: z => + z + .union([z.string(), z.boolean()], { + description: + 'If set, only files with the given extension will be templated. If set to `true`, the default extension `.njk` is used.', + }) + .optional(), + replace: z => + z + .boolean({ + description: + 'If set, replace files in targetPath instead of skipping existing ones.', + }) + .optional(), + trimBlocks: z => + z + .boolean({ + description: + 'If set, the first newline after a block is removed (block, not variable tag).', + }) + .optional(), + lstripBlocks: z => + z + .boolean({ + description: + 'If set, leading spaces and tabs are stripped from the start of a line to a block.', + }) + .optional(), + token: z => + z + .string({ + description: + 'An optional token to use for authentication when reading the resources.', + }) + .optional(), }, }, supportsDryRun: true, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateActionHandler.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateActionHandler.ts index f280841bfa..e436616651 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateActionHandler.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateActionHandler.ts @@ -36,12 +36,6 @@ export type TemplateActionInput = { targetPath?: string; values: any; templateFileExtension?: string | boolean; - - // Cookiecutter compat options - /** - * @deprecated This field is deprecated in favor of copyWithoutTemplating. - */ - copyWithoutRender?: string[]; copyWithoutTemplating?: string[]; cookiecutterCompat?: boolean; replace?: boolean; @@ -206,26 +200,12 @@ function resolveTemplateActionSettings( const targetPath = ctx.input.targetPath ?? './'; const outputDir = resolveSafeChildPath(ctx.workspacePath, targetPath); - if (ctx.input.copyWithoutRender && ctx.input.copyWithoutTemplating) { - throw new InputError( - 'Fetch action input copyWithoutRender and copyWithoutTemplating can not be used at the same time', - ); - } - let copyOnlyPatterns: string[] | undefined; - let renderFilename: boolean; - if (ctx.input.copyWithoutRender) { - ctx.logger.warn( - '[Deprecated] copyWithoutRender is deprecated Please use copyWithoutTemplating instead.', - ); - copyOnlyPatterns = ctx.input.copyWithoutRender; - renderFilename = false; - } else { - copyOnlyPatterns = ctx.input.copyWithoutTemplating; - renderFilename = true; - } + const copyOnlyPatterns = ctx.input.copyWithoutTemplating; + const renderFilename = true; + if (copyOnlyPatterns && !Array.isArray(copyOnlyPatterns)) { throw new InputError( - 'Fetch action input copyWithoutRender/copyWithoutTemplating must be an Array', + 'Fetch action input copyWithoutTemplating must be an Array', ); } if ( @@ -233,7 +213,7 @@ function resolveTemplateActionSettings( (copyOnlyPatterns || ctx.input.cookiecutterCompat) ) { throw new InputError( - 'Fetch action input extension incompatible with copyWithoutRender/copyWithoutTemplating and cookiecutterCompat', + 'Fetch action input extension incompatible with copyWithoutTemplating and cookiecutterCompat', ); } let extension: string | false = false; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.ts index e70e9435c5..2cfe16f900 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/templateFile.ts @@ -38,60 +38,63 @@ export function createFetchTemplateFileAction(options: { additionalTemplateFilters?: Record; additionalTemplateGlobals?: Record; }) { - return createTemplateAction<{ - url: string; - targetPath: string; - values: any; - cookiecutterCompat?: boolean; - replace?: boolean; - trimBlocks?: boolean; - lstripBlocks?: boolean; - token?: string; - }>({ + return createTemplateAction({ id: 'fetch:template:file', description: 'Downloads single file and places it in the workspace.', examples, schema: { input: { - type: 'object', - required: ['url', 'targetPath'], - properties: { - url: { - title: 'Fetch URL', + url: z => + z.string({ description: 'Relative path or absolute URL pointing to the single file to fetch.', - type: 'string', - }, - targetPath: { - title: 'Target Path', + }), + targetPath: z => + z.string({ description: 'Target path within the working directory to download the file as.', - type: 'string', - }, - values: { - title: 'Template Values', - description: 'Values to pass on to the templating engine', - type: 'object', - }, - cookiecutterCompat: { - title: 'Cookiecutter compatibility mode', - description: - 'Enable features to maximise compatibility with templates built for fetch:cookiecutter', - type: 'boolean', - }, - replace: { - title: 'Replace file', - description: - 'If set, replace file in targetPath instead of overwriting existing one.', - type: 'boolean', - }, - token: { - title: 'Token', - description: - 'An optional token to use for authentication when reading the resources.', - type: 'string', - }, - }, + }), + values: z => + z + .record(z.any(), { + description: 'Values to pass on to the templating engine', + }) + .optional(), + cookiecutterCompat: z => + z + .boolean({ + description: + 'Enable features to maximise compatibility with templates built for fetch:cookiecutter', + }) + .optional(), + replace: z => + z + .boolean({ + description: + 'If set, replace file in targetPath instead of overwriting existing one.', + }) + .optional(), + trimBlocks: z => + z + .boolean({ + description: + 'If set, the first newline after a block is removed (block, not variable tag).', + }) + .optional(), + lstripBlocks: z => + z + .boolean({ + description: + 'If set, leading spaces and tabs are stripped from the start of a line to a block.', + }) + .optional(), + token: z => + z + .string({ + description: + 'An optional token to use for authentication when reading the resources.', + }) + .optional(), }, }, supportsDryRun: true, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/workspaceTemplate.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/workspaceTemplate.test.ts index 92b17aa2c9..2a9b4ef68e 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/workspaceTemplate.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/workspaceTemplate.test.ts @@ -92,7 +92,7 @@ describe('workspace:template', () => { }), ), ).rejects.toThrow( - /input extension incompatible with copyWithoutRender\/copyWithoutTemplating and cookiecutterCompat/, + /input extension incompatible with copyWithoutTemplating and cookiecutterCompat/, ); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts index 5f894a4b3e..0290414323 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts @@ -26,24 +26,16 @@ import { examples } from './delete.examples'; * @public */ export const createFilesystemDeleteAction = () => { - return createTemplateAction<{ files: string[] }>({ + return createTemplateAction({ id: 'fs:delete', description: 'Deletes files and directories from the workspace', examples, schema: { input: { - required: ['files'], - type: 'object', - properties: { - files: { - title: 'Files', + files: z => + z.array(z.string(), { description: 'A list of files and directories that will be deleted', - type: 'array', - items: { - type: 'string', - }, - }, - }, + }), }, }, supportsDryRun: true, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/read.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/read.ts index 0351e7cb0e..3ab6526b4b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/read.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/read.ts @@ -14,19 +14,21 @@ * limitations under the License. */ import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; -import z from 'zod'; import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import fs from 'fs/promises'; import path from 'path'; +import { z as zod } from 'zod'; -const contentSchema = z.object({ - name: z.string().describe('Name of the file or directory'), - path: z - .string() - .describe('path to the file or directory relative to the workspace'), - fullPath: z.string().describe('full path to the file or directory'), -}); -type Content = z.infer; +const contentSchema = (z: typeof zod) => + z.object({ + name: z.string().describe('Name of the file or directory'), + path: z + .string() + .describe('path to the file or directory relative to the workspace'), + fullPath: z.string().describe('full path to the file or directory'), + }); + +type Content = zod.infer>; /** * Creates new action that enables reading directories in the workspace. @@ -38,14 +40,14 @@ export const createFilesystemReadDirAction = () => { description: 'Reads files and directories from the workspace', supportsDryRun: true, schema: { - input: z.object({ - paths: z.array(z.string().min(1)), - recursive: z.boolean().default(false), - }), - output: z.object({ - files: z.array(contentSchema), - folders: z.array(contentSchema), - }), + input: { + paths: z => z.array(z.string().min(1)), + recursive: z => z.boolean().default(false), + }, + output: { + files: z => z.array(contentSchema(z)), + folders: z => z.array(contentSchema(z)), + }, }, async handler(ctx) { const files: Content[] = []; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/rename.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/rename.ts index 238fc9e8c0..7ced3ca6b0 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/rename.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/rename.ts @@ -16,56 +16,42 @@ import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; -import { InputError } from '@backstage/errors'; import fs from 'fs-extra'; import { examples } from './rename.examples'; +import { InputError } from '@backstage/errors'; /** * Creates a new action that allows renames of files and directories in the workspace. * @public */ export const createFilesystemRenameAction = () => { - return createTemplateAction<{ - files: Array<{ - from: string; - to: string; - overwrite?: boolean; - }>; - }>({ + return createTemplateAction({ id: 'fs:rename', description: 'Renames files and directories within the workspace', examples, schema: { input: { - required: ['files'], - type: 'object', - properties: { - files: { - title: 'Files', - description: - 'A list of file and directory names that will be renamed', - type: 'array', - items: { - type: 'object', - required: ['from', 'to'], - properties: { - from: { - type: 'string', - title: 'The source location of the file to be renamed', - }, - to: { - type: 'string', - title: 'The destination of the new file', - }, - overwrite: { - type: 'boolean', - title: + files: z => + z.array( + z.object({ + from: z.string({ + description: 'The source location of the file to be renamed', + }), + to: z.string({ + description: 'The destination of the new file', + }), + overwrite: z + .boolean({ + description: 'Overwrite existing file or directory, default is false', - }, - }, + }) + .optional(), + }), + { + description: + 'A list of file and directory names that will be renamed', }, - }, - }, + ), }, }, supportsDryRun: true, @@ -78,7 +64,6 @@ export const createFilesystemRenameAction = () => { if (!file.from || !file.to) { throw new InputError('each file must have a from and to property'); } - const sourceFilepath = resolveSafeChildPath( ctx.workspacePath, file.from, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts index 6fdbb24484..a4501c805f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts @@ -15,7 +15,6 @@ */ export * from './catalog'; -export * from './createBuiltinActions'; export * from './debug'; export * from './fetch'; export * from './filesystem'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/deprecated.ts b/plugins/scaffolder-backend/src/scaffolder/actions/deprecated.ts deleted file mode 100644 index 324e9b43e1..0000000000 --- a/plugins/scaffolder-backend/src/scaffolder/actions/deprecated.ts +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { - createGithubActionsDispatchAction as githubActionsDispatch, - createGithubDeployKeyAction as githubDeployKey, - createGithubEnvironmentAction as githubEnvironment, - createGithubIssuesLabelAction as githubIssuesLabel, - CreateGithubPullRequestActionOptions as GithubPullRequestActionOptions, - createGithubRepoCreateAction as githubRepoCreate, - createGithubRepoPushAction as githubRepoPush, - createGithubWebhookAction as githubWebhook, - createPublishGithubAction as publishGithub, - createPublishGithubPullRequestAction as publishGithubPullRequest, -} from '@backstage/plugin-scaffolder-backend-module-github'; - -import { - createPublishGitlabAction as publishGitlab, - createPublishGitlabMergeRequestAction as publishGitlabMergeRequest, -} from '@backstage/plugin-scaffolder-backend-module-gitlab'; - -import { createPublishAzureAction as publishAzure } from '@backstage/plugin-scaffolder-backend-module-azure'; - -import { createPublishBitbucketAction as publishBitbucket } from '@backstage/plugin-scaffolder-backend-module-bitbucket'; - -import { createPublishBitbucketCloudAction as publishBitbucketCloud } from '@backstage/plugin-scaffolder-backend-module-bitbucket-cloud'; - -import { - createPublishBitbucketServerAction as publishBitbucketServer, - createPublishBitbucketServerPullRequestAction as publishBitbucketServerPullRequest, -} from '@backstage/plugin-scaffolder-backend-module-bitbucket-server'; - -import { - createPublishGerritAction as publishGerrit, - createPublishGerritReviewAction as publishGerritReview, -} from '@backstage/plugin-scaffolder-backend-module-gerrit'; - -/** - * @public - * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-github` instead - */ -export const createGithubActionsDispatchAction = githubActionsDispatch; - -/** - * @public - * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-github` instead - */ -export const createGithubDeployKeyAction = githubDeployKey; - -/** - * @public - * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-github` instead - */ -export const createGithubEnvironmentAction = githubEnvironment; - -/** - * @public - * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-github` instead - */ -export const createGithubIssuesLabelAction = githubIssuesLabel; - -/** - * @public - * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-github` instead - */ -export type CreateGithubPullRequestActionOptions = - GithubPullRequestActionOptions; - -/** - * @public - * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-github` instead - */ -export const createGithubRepoCreateAction = githubRepoCreate; - -/** - * @public - * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-github` instead - */ -export const createGithubRepoPushAction = githubRepoPush; - -/** - * @public - * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-github` instead - */ -export const createGithubWebhookAction = githubWebhook; - -/** - * @public - * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-github` instead - */ -export const createPublishGithubAction = publishGithub; - -/** - * @public - * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-github` instead - */ -export const createPublishGithubPullRequestAction = publishGithubPullRequest; - -/** - * @public @deprecated use "createPublishBitbucketCloudAction" from \@backstage/plugin-scaffolder-backend-module-bitbucket-cloud or "createPublishBitbucketServerAction" from \@backstage/plugin-scaffolder-backend-module-bitbucket-server instead - */ -export const createPublishBitbucketAction = publishBitbucket; - -/** - * @public - * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-bitbucket-cloud` instead - */ -export const createPublishBitbucketCloudAction = publishBitbucketCloud; - -/** - * @public - * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-bitbucket-server` instead - */ -export const createPublishBitbucketServerAction = publishBitbucketServer; - -/** - * @public - * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-bitbucket-server` instead - */ -export const createPublishBitbucketServerPullRequestAction = - publishBitbucketServerPullRequest; - -/** - * @public - * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-azure` instead - */ -export const createPublishAzureAction = publishAzure; - -/** - * @public - * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-gerrit` instead - */ -export const createPublishGerritAction = publishGerrit; - -/** - * @public - * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-gerrit` instead - */ -export const createPublishGerritReviewAction = publishGerritReview; - -/** - * @public - * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-gitlab` instead - */ -export const createPublishGitlabAction = publishGitlab; - -/** - * @public - * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-gitlab` instead - */ -export const createPublishGitlabMergeRequestAction = publishGitlabMergeRequest; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/index.ts b/plugins/scaffolder-backend/src/scaffolder/actions/index.ts index 8fdad68499..ad128b19b7 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/index.ts @@ -15,6 +15,5 @@ */ export * from './builtin'; -export * from './deprecated'; export { TemplateActionRegistry } from './TemplateActionRegistry'; diff --git a/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts b/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts index aa5a3f5119..9d4494c536 100644 --- a/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts @@ -17,6 +17,7 @@ import { AuditorService, BackstageCredentials, + LoggerService, } from '@backstage/backend-plugin-api'; import type { UserEntity } from '@backstage/catalog-model'; import { ScmIntegrations } from '@backstage/integration'; @@ -36,7 +37,6 @@ import fs from 'fs-extra'; import path from 'path'; import { fileURLToPath } from 'url'; import { v4 as uuid } from 'uuid'; -import { Logger } from 'winston'; import { TemplateActionRegistry } from '../actions'; import { NunjucksWorkflowRunner } from '../tasks/NunjucksWorkflowRunner'; import { DecoratedActionsRegistry } from './DecoratedActionsRegistry'; @@ -61,7 +61,7 @@ interface DryRunResult { /** @internal */ export type TemplateTesterCreateOptions = { - logger: Logger; + logger: LoggerService; auditor?: AuditorService; integrations: ScmIntegrations; actionRegistry: TemplateActionRegistry; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts index 51a30a20ec..f81a79a5a0 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts @@ -76,7 +76,7 @@ export type RawDbTaskEventRow = { /** * DatabaseTaskStore - * + * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. * @public */ export type DatabaseTaskStoreOptions = { @@ -86,9 +86,7 @@ export type DatabaseTaskStoreOptions = { /** * Type guard to help DatabaseTaskStore understand when database is DatabaseService vs. when database is a Knex instance. - * - * * @public - */ + * */ function isDatabaseService( opt: DatabaseService | Knex, ): opt is DatabaseService { @@ -111,7 +109,7 @@ const parseSqlDateToIsoString = (input: T): T | string => { /** * DatabaseTaskStore - * + * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. * @public */ export class DatabaseTaskStore implements TaskStore { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts index 2d6ed2d57c..a4876f2edb 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts @@ -23,11 +23,9 @@ import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { createTemplateAction, TaskSecrets, - TemplateAction, TaskContext, } from '@backstage/plugin-scaffolder-node'; import { UserEntity } from '@backstage/catalog-model'; -import { z } from 'zod'; import { AuthorizeResult, PermissionEvaluator, @@ -38,17 +36,15 @@ import { mockCredentials, mockServices, } from '@backstage/backend-test-utils'; -import { LoggerService } from '@backstage/backend-plugin-api'; -import { loggerToWinstonLogger } from '../../util/loggerToWinstonLogger'; describe('NunjucksWorkflowRunner', () => { - let logger: LoggerService; - let actionRegistry = new TemplateActionRegistry(); + let actionRegistry: TemplateActionRegistry; let runner: NunjucksWorkflowRunner; let fakeActionHandler: jest.Mock; let fakeTaskLog: jest.Mock; let stripAnsi: typeof import('strip-ansi').default; + const logger = mockServices.logger.mock(); const mockDir = createMockDirectory(); const mockedPermissionApi: jest.Mocked = { @@ -71,11 +67,21 @@ describe('NunjucksWorkflowRunner', () => { }); const createMockTaskWithSpec = ( - spec: TaskSpec, + { + apiVersion = 'scaffolder.backstage.io/v1beta3', + output = {}, + parameters = {}, + ...spec + }: Partial, secrets?: TaskSecrets, isDryRun?: boolean, ): TaskContext => ({ - spec, + spec: { + apiVersion, + output, + parameters, + ...spec, + } as TaskSpec, secrets, isDryRun, complete: async () => {}, @@ -98,8 +104,6 @@ describe('NunjucksWorkflowRunner', () => { // This one is ESM-only stripAnsi = await import('strip-ansi').then(m => m.default); - jest.resetAllMocks(); - logger = mockServices.logger.mock(); actionRegistry = new TemplateActionRegistry(); fakeActionHandler = jest.fn(); fakeTaskLog = jest.fn(); @@ -120,32 +124,12 @@ describe('NunjucksWorkflowRunner', () => { handler: fakeActionHandler, schema: { input: { - type: 'object', - required: ['foo'], - properties: { - foo: { - type: 'number', - }, - }, + foo: z => z.number(), }, }, }), ); - actionRegistry.register( - createTemplateAction({ - id: 'jest-legacy-zod-validated-action', - description: 'Mock action for testing', - handler: fakeActionHandler, - supportsDryRun: true, - schema: { - input: z.object({ - foo: z.number(), - }), - }, - }) as TemplateAction, - ); - actionRegistry.register( createTemplateAction({ id: 'jest-zod-validated-action', @@ -179,13 +163,14 @@ describe('NunjucksWorkflowRunner', () => { id: 'checkpoints-action', description: 'Mock action with checkpoints', schema: { - output: z.object({ - key1: z.string(), - key2: z.string(), - key3: z.string(), - key4: z.string(), - key5: z.string(), - }), + output: z => + z.object({ + key1: z.string(), + key2: z.string(), + key3: z.string(), + key4: z.string(), + key5: z.string(), + }), }, handler: async ctx => { const key1 = await ctx.checkpoint({ @@ -215,7 +200,9 @@ describe('NunjucksWorkflowRunner', () => { ctx.output('key2', key2); ctx.output('key3', key3); + // @ts-expect-error - not valid output type ctx.output('key4', key4); + // @ts-expect-error - not valid output type ctx.output('key5', key5); }, }), @@ -229,16 +216,19 @@ describe('NunjucksWorkflowRunner', () => { actionRegistry, integrations, workingDirectory: mockDir.path, - logger: loggerToWinstonLogger(logger), + logger, permissions: mockedPermissionApi, }); }); + afterEach(() => { + mockDir.clear(); + + jest.resetAllMocks(); + }); + it('should throw an error if the action does not exist', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, steps: [{ id: 'test', name: 'name', action: 'does-not-exist' }], }); @@ -250,9 +240,6 @@ describe('NunjucksWorkflowRunner', () => { describe('validation', () => { it('should throw an error if the action has a schema and the input does not match', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, steps: [{ id: 'test', name: 'name', action: 'jest-validated-action' }], }); @@ -263,9 +250,6 @@ describe('NunjucksWorkflowRunner', () => { it('should throw an error if the action has a zod schema and the input does not match', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, steps: [ { id: 'test', name: 'name', action: 'jest-zod-validated-action' }, ], @@ -276,30 +260,8 @@ describe('NunjucksWorkflowRunner', () => { ); }); - it('should throw an error if the action has legacy zod schema and the input does not match', async () => { - const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, - steps: [ - { - id: 'test', - name: 'name', - action: 'jest-legacy-zod-validated-action', - }, - ], - }); - - await expect(runner.execute(task)).rejects.toThrow( - /Invalid input passed to action jest-legacy-zod-validated-action, instance requires property \"foo\"/, - ); - }); - it('should run the action when the zod validation passes', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, steps: [ { id: 'test', @@ -315,31 +277,8 @@ describe('NunjucksWorkflowRunner', () => { expect(fakeActionHandler).toHaveBeenCalledTimes(1); }); - it('should run the action when the zod validation passes with legacy zod', async () => { - const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, - steps: [ - { - id: 'test', - name: 'name', - action: 'jest-legacy-zod-validated-action', - input: { foo: 1 }, - }, - ], - }); - - await runner.execute(task); - - expect(fakeActionHandler).toHaveBeenCalledTimes(1); - }); - it('should run the action when the validation passes', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, steps: [ { id: 'test', @@ -373,9 +312,6 @@ describe('NunjucksWorkflowRunner', () => { }; const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, steps: [ { id: 'test', @@ -404,9 +340,6 @@ describe('NunjucksWorkflowRunner', () => { it('should pass token through', async () => { const task = createMockTaskWithSpec( { - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, steps: [ { id: 'test', @@ -433,7 +366,6 @@ describe('NunjucksWorkflowRunner', () => { describe('conditionals', () => { it('should execute steps conditionally', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', name: 'test', action: 'output-action' }, { @@ -446,7 +378,6 @@ describe('NunjucksWorkflowRunner', () => { output: { result: '${{ steps.conditional.output.mock }}', }, - parameters: {}, }); const { output } = await runner.execute(task); @@ -456,7 +387,6 @@ describe('NunjucksWorkflowRunner', () => { it('should skips steps conditionally', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', name: 'test', action: 'output-action' }, { @@ -469,7 +399,6 @@ describe('NunjucksWorkflowRunner', () => { output: { result: '${{ steps.conditional.output.mock }}', }, - parameters: {}, }); const { output } = await runner.execute(task); @@ -479,7 +408,6 @@ describe('NunjucksWorkflowRunner', () => { it('should skips steps using the negating equals operator', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', name: 'test', action: 'output-action' }, { @@ -492,7 +420,6 @@ describe('NunjucksWorkflowRunner', () => { output: { result: '${{ steps.conditional.output.mock }}', }, - parameters: {}, }); const { output } = await runner.execute(task); @@ -502,7 +429,6 @@ describe('NunjucksWorkflowRunner', () => { describe('should apply boolean step conditions', () => { it('executes when true', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'conditional', @@ -514,7 +440,6 @@ describe('NunjucksWorkflowRunner', () => { output: { result: '${{ steps.conditional.output.mock }}', }, - parameters: {}, }); const { output } = await runner.execute(task); @@ -522,7 +447,6 @@ describe('NunjucksWorkflowRunner', () => { }); it('skips when false', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'conditional', @@ -534,7 +458,6 @@ describe('NunjucksWorkflowRunner', () => { output: { result: '${{ steps.conditional.output.mock }}', }, - parameters: {}, }); const { output } = await runner.execute(task); @@ -546,7 +469,6 @@ describe('NunjucksWorkflowRunner', () => { describe('templating', () => { it('should template the input to an action', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -557,7 +479,6 @@ describe('NunjucksWorkflowRunner', () => { }, }, ], - output: {}, parameters: { input: 'BACKSTAGE', }, @@ -573,7 +494,6 @@ describe('NunjucksWorkflowRunner', () => { it('should not try and parse something that is not parsable', async () => { jest.spyOn(logger, 'error'); const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -584,7 +504,6 @@ describe('NunjucksWorkflowRunner', () => { }, }, ], - output: {}, parameters: { input: 'BACKSTAGE', }, @@ -597,7 +516,6 @@ describe('NunjucksWorkflowRunner', () => { it('should keep the original types for the input and not parse things that are not meant to be parsed', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -609,7 +527,6 @@ describe('NunjucksWorkflowRunner', () => { }, }, ], - output: {}, parameters: { number: 0, string: '1', @@ -625,7 +542,6 @@ describe('NunjucksWorkflowRunner', () => { it('should template complex values into the action', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -636,7 +552,6 @@ describe('NunjucksWorkflowRunner', () => { }, }, ], - output: {}, parameters: { complex: { bar: 'BACKSTAGE' }, }, @@ -651,7 +566,6 @@ describe('NunjucksWorkflowRunner', () => { it('supports really complex structures', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -662,7 +576,6 @@ describe('NunjucksWorkflowRunner', () => { }, }, ], - output: {}, parameters: { complex: { bar: 'BACKSTAGE', @@ -680,7 +593,6 @@ describe('NunjucksWorkflowRunner', () => { it('supports numbers as first class too', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -691,7 +603,6 @@ describe('NunjucksWorkflowRunner', () => { }, }, ], - output: {}, parameters: { complex: { bar: 'BACKSTAGE', @@ -710,8 +621,6 @@ describe('NunjucksWorkflowRunner', () => { it('should deal with checkpoints', async () => { const task = { ...createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, steps: [ { id: 'test', @@ -762,7 +671,6 @@ describe('NunjucksWorkflowRunner', () => { it('should template the output from simple actions', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -774,7 +682,6 @@ describe('NunjucksWorkflowRunner', () => { output: { foo: '${{steps.test.output.mock | upper}}', }, - parameters: {}, }); const { output } = await runner.execute(task); @@ -784,7 +691,6 @@ describe('NunjucksWorkflowRunner', () => { it('should include task ID in the templated context', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -797,8 +703,6 @@ describe('NunjucksWorkflowRunner', () => { }, }, ], - output: {}, - parameters: {}, }); await runner.execute(task); @@ -836,9 +740,6 @@ describe('NunjucksWorkflowRunner', () => { const task = createMockTaskWithSpec( { - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, steps: [ { id: 'test', @@ -882,9 +783,6 @@ describe('NunjucksWorkflowRunner', () => { const task = createMockTaskWithSpec( { - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, steps: [ { id: 'test', @@ -909,7 +807,6 @@ describe('NunjucksWorkflowRunner', () => { it('should run a step repeatedly - flat values', async () => { const colors = ['blue', 'green', 'red']; const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -919,7 +816,6 @@ describe('NunjucksWorkflowRunner', () => { input: { color: '${{each.value}}' }, }, ], - output: {}, parameters: { colors, }, @@ -938,7 +834,6 @@ describe('NunjucksWorkflowRunner', () => { it('should run a step repeatedly - object list', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -951,7 +846,6 @@ describe('NunjucksWorkflowRunner', () => { }, }, ], - output: {}, parameters: { settings: [{ color: 'blue' }], }, @@ -974,7 +868,6 @@ describe('NunjucksWorkflowRunner', () => { transparent: 'yes', }; const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -984,7 +877,6 @@ describe('NunjucksWorkflowRunner', () => { input: { key: '${{each.key}}', value: '${{each.value}}' }, }, ], - output: {}, parameters: { settings, }, @@ -1006,7 +898,6 @@ describe('NunjucksWorkflowRunner', () => { it('should run a step repeatedly with validation of single-expression value', async () => { const numbers = [5, 7, 9]; const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -1016,7 +907,6 @@ describe('NunjucksWorkflowRunner', () => { input: { foo: '${{each.value}}' }, }, ], - output: {}, parameters: { numbers, }, @@ -1037,7 +927,6 @@ describe('NunjucksWorkflowRunner', () => { it('should validate each action iteration', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -1047,7 +936,6 @@ describe('NunjucksWorkflowRunner', () => { input: { foo: '${{each.value.foo}}' }, }, ], - output: {}, parameters: { data: [ { @@ -1065,7 +953,6 @@ describe('NunjucksWorkflowRunner', () => { it('should validate each parameter renders to a valid value', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -1075,8 +962,6 @@ describe('NunjucksWorkflowRunner', () => { input: { foo: '${{each.value}}' }, }, ], - output: {}, - parameters: {}, }); await expect(runner.execute(task)).rejects.toThrow( 'Invalid value on action jest-validated-action.each parameter, "${{parameters.data}}" cannot be resolved to a value', @@ -1089,7 +974,6 @@ describe('NunjucksWorkflowRunner', () => { it('should pass through the secrets to the context', async () => { const task = createMockTaskWithSpec( { - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -1098,8 +982,6 @@ describe('NunjucksWorkflowRunner', () => { input: {}, }, ], - output: {}, - parameters: {}, }, { foo: 'bar' }, ); @@ -1114,7 +996,6 @@ describe('NunjucksWorkflowRunner', () => { it('should be able to template secrets into the input of an action', async () => { const task = createMockTaskWithSpec( { - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -1125,8 +1006,6 @@ describe('NunjucksWorkflowRunner', () => { }, }, ], - output: {}, - parameters: {}, }, { foo: 'bar' }, ); @@ -1141,7 +1020,6 @@ describe('NunjucksWorkflowRunner', () => { it('does not allow templating of secrets as an output', async () => { const task = createMockTaskWithSpec( { - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -1155,7 +1033,6 @@ describe('NunjucksWorkflowRunner', () => { output: { b: '${{ secrets.foo }}', }, - parameters: {}, }, { foo: 'bar' }, ); @@ -1169,7 +1046,6 @@ describe('NunjucksWorkflowRunner', () => { describe('user', () => { it('allows access to the user entity at the templating level', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -1199,7 +1075,6 @@ describe('NunjucksWorkflowRunner', () => { describe('filters', () => { it('provides the parseRepoUrl filter', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -1228,7 +1103,6 @@ describe('NunjucksWorkflowRunner', () => { describe('parseEntityRef', () => { it('parses entity ref', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -1256,7 +1130,6 @@ describe('NunjucksWorkflowRunner', () => { it('provides default kind for parsing entity ref', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -1284,7 +1157,6 @@ describe('NunjucksWorkflowRunner', () => { it('provides default namespace for parsing entity ref', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -1312,7 +1184,6 @@ describe('NunjucksWorkflowRunner', () => { it('provides default kind and namespace for parsing entity ref', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -1342,7 +1213,6 @@ describe('NunjucksWorkflowRunner', () => { 'ignores invalid context "%s" for parsing entity refF', async kind => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -1371,7 +1241,6 @@ describe('NunjucksWorkflowRunner', () => { it('fails when unable to parse entity ref', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -1398,7 +1267,6 @@ describe('NunjucksWorkflowRunner', () => { it('provides the pick filter', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -1422,7 +1290,6 @@ describe('NunjucksWorkflowRunner', () => { it('should allow deep nesting of picked objects', async () => { const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ { id: 'test', @@ -1455,9 +1322,6 @@ describe('NunjucksWorkflowRunner', () => { it('sets isDryRun flag correctly', async () => { const task = createMockTaskWithSpec( { - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, steps: [ { id: 'test', @@ -1481,13 +1345,10 @@ describe('NunjucksWorkflowRunner', () => { it('should have metadata in action context during dry run', async () => { const task = createMockTaskWithSpec( { - apiVersion: 'scaffolder.backstage.io/v1beta3', templateInfo: { entityRef: 'dryRun-Entity', entity: { metadata: { name: 'test-template' } }, }, - parameters: {}, - output: {}, steps: [ { id: 'test', @@ -1519,9 +1380,6 @@ describe('NunjucksWorkflowRunner', () => { ]); const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, steps: [ { id: 'test', @@ -1560,9 +1418,6 @@ describe('NunjucksWorkflowRunner', () => { ]); const task = createMockTaskWithSpec({ - apiVersion: 'scaffolder.backstage.io/v1beta3', - parameters: {}, - output: {}, steps: [ { id: 'test1', diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 80e9517fd6..a66ae0e789 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -28,7 +28,6 @@ import fs from 'fs-extra'; import { validate as validateJsonSchema } from 'jsonschema'; import nunjucks from 'nunjucks'; import path from 'path'; -import { PassThrough } from 'stream'; import * as winston from 'winston'; import { SecureTemplater, @@ -40,6 +39,7 @@ import { TaskTrackType, WorkflowResponse, WorkflowRunner } from './types'; import type { AuditorService, + LoggerService, PermissionsService, } from '@backstage/backend-plugin-api'; import { UserEntity } from '@backstage/catalog-model'; @@ -59,14 +59,13 @@ import { createDefaultFilters } from '../../lib/templating/filters/createDefault import { scaffolderActionRules } from '../../service/rules'; import { createCounterMetric, createHistogramMetric } from '../../util/metrics'; import { BackstageLoggerTransport, WinstonLogger } from './logger'; -import { loggerToWinstonLogger } from '../../util/loggerToWinstonLogger'; import { convertFiltersToRecord } from '../../util/templating'; type NunjucksWorkflowRunnerOptions = { workingDirectory: string; actionRegistry: TemplateActionRegistry; integrations: ScmIntegrations; - logger: winston.Logger; + logger: LoggerService; auditor?: AuditorService; additionalTemplateFilters?: Record; additionalTemplateGlobals?: Record; @@ -113,7 +112,7 @@ const createStepLogger = ({ }: { task: TaskContext; step: TaskStep; - rootLogger: winston.Logger; + rootLogger: LoggerService; }) => { const taskLogger = WinstonLogger.create({ level: process.env.LOG_LEVEL || 'info', @@ -126,22 +125,7 @@ const createStepLogger = ({ taskLogger.addRedactions(Object.values(task.secrets ?? {})); - // This stream logger should be deprecated. We're going to replace it with - // just using the logger directly, as all those logs get written to step logs - // using the stepLogStream above. - // Initially this stream used to be the only way to write to the client logs, but that - // has changed over time, there's not really a need for this anymore. - // You can just create a simple wrapper like the below in your action to write to the main logger. - // This way we also get redactions for free. - const streamLogger = new PassThrough(); - streamLogger.on('data', async data => { - const message = data.toString().trim(); - if (message?.length > 1) { - taskLogger.info(message); - } - }); - - return { taskLogger, streamLogger }; + return { taskLogger }; }; const isActionAuthorized = createConditionAuthorizer( @@ -268,7 +252,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { } const action: TemplateAction = this.options.actionRegistry.get(step.action); - const { taskLogger, streamLogger } = createStepLogger({ + const { taskLogger } = createStepLogger({ task, step, rootLogger: this.options.logger, @@ -393,9 +377,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { id: await task.getWorkspaceName(), }, secrets: task.secrets ?? {}, - // TODO(blam): move to LoggerService and away from Winston - logger: loggerToWinstonLogger(taskLogger), - logStream: streamLogger, + logger: taskLogger, workspacePath, async checkpoint(opts: { key?: string; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index 27a4499772..86f9f7fcf7 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -18,6 +18,7 @@ import { AuditorService, AuthService, BackstageCredentials, + LoggerService, } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; @@ -38,7 +39,6 @@ import { Observable, createDeferred, } from '@backstage/types'; -import { Logger } from 'winston'; import ObservableImpl from 'zen-observable'; import { DefaultWorkspaceService, WorkspaceService } from './WorkspaceService'; import { readDuration } from './helper'; @@ -59,7 +59,7 @@ type TaskState = { }; /** * TaskManager - * + * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. * @public */ export class TaskManager implements TaskContext { @@ -71,7 +71,7 @@ export class TaskManager implements TaskContext { task: CurrentClaimedTask, storage: TaskStore, abortSignal: AbortSignal, - logger: Logger, + logger: LoggerService, auth?: AuthService, config?: Config, additionalWorkspaceProviders?: Record, @@ -100,7 +100,7 @@ export class TaskManager implements TaskContext { private readonly task: CurrentClaimedTask, private readonly storage: TaskStore, private readonly signal: AbortSignal, - private readonly logger: Logger, + private readonly logger: LoggerService, private readonly workspaceService: WorkspaceService, private readonly auth?: AuthService, ) {} @@ -269,7 +269,7 @@ export interface CurrentClaimedTask { export class StorageTaskBroker implements TaskBroker { constructor( private readonly storage: TaskStore, - private readonly logger: Logger, + private readonly logger: LoggerService, private readonly config?: Config, private readonly auth?: AuthService, private readonly additionalWorkspaceProviders?: Record< diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index 4441a2f0ca..b0c3166ff6 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { AuditorService } from '@backstage/backend-plugin-api'; +import { AuditorService, LoggerService } from '@backstage/backend-plugin-api'; import { assertError, stringifyError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; @@ -25,7 +25,6 @@ import { TemplateGlobal, } from '@backstage/plugin-scaffolder-node'; import PQueue from 'p-queue'; -import { Logger } from 'winston'; import { TemplateActionRegistry } from '../actions'; import { NunjucksWorkflowRunner } from './NunjucksWorkflowRunner'; import { WorkflowRunner } from './types'; @@ -33,7 +32,7 @@ import { setTimeout } from 'timers/promises'; /** * TaskWorkerOptions - * + * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. * @public */ export type TaskWorkerOptions = { @@ -43,14 +42,14 @@ export type TaskWorkerOptions = { }; concurrentTasksLimit: number; permissions?: PermissionEvaluator; - logger?: Logger; + logger?: LoggerService; auditor?: AuditorService; gracefulShutdown?: boolean; }; /** * CreateWorkerOptions - * + * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. * @public */ export type CreateWorkerOptions = { @@ -58,7 +57,7 @@ export type CreateWorkerOptions = { actionRegistry: TemplateActionRegistry; integrations: ScmIntegrations; workingDirectory: string; - logger: Logger; + logger: LoggerService; auditor?: AuditorService; additionalTemplateFilters?: Record; /** @@ -81,12 +80,12 @@ export type CreateWorkerOptions = { /** * TaskWorker - * + * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. * @public */ export class TaskWorker { private taskQueue: PQueue; - private logger: Logger | undefined; + private logger: LoggerService | undefined; private auditor: AuditorService | undefined; private stopWorkers: boolean; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts index 9d231d7e7c..265151d73b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts @@ -21,19 +21,10 @@ export type { CurrentClaimedTask } from './StorageTaskBroker'; export { TaskWorker } from './TaskWorker'; export type { CreateWorkerOptions } from './TaskWorker'; export type { - TaskCompletionState, TaskStoreEmitOptions, TaskStoreListEventsOptions, TaskStoreShutDownTaskOptions, - SerializedTask, - SerializedTaskEvent, - TaskStatus, - TaskEventType, - TaskBroker, - TaskContext, TaskStore, - TaskBrokerDispatchResult, - TaskBrokerDispatchOptions, TaskStoreCreateTaskOptions, TaskStoreRecoverTaskOptions, TaskStoreCreateTaskResult, diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts index 1556c4838c..a3d9e3b741 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts @@ -17,96 +17,19 @@ import { HumanDuration, JsonObject, JsonValue } from '@backstage/types'; import { TaskSpec, TaskStep } from '@backstage/plugin-scaffolder-common'; import { - SerializedTask as _SerializedTask, - SerializedTaskEvent as _SerializedTaskEvent, - TaskBroker as _TaskBroker, - TaskBrokerDispatchOptions as _TaskBrokerDispatchOptions, - TaskBrokerDispatchResult as _TaskBrokerDispatchResult, - TaskCompletionState as _TaskCompletionState, - TaskContext as _TaskContext, - TaskEventType as _TaskEventType, TaskSecrets, - TaskStatus as _TaskStatus, TemplateAction, + TaskContext, + SerializedTaskEvent, + SerializedTask, + TaskStatus, } from '@backstage/plugin-scaffolder-node'; -/** - * The status of each step of the Task - * - * @public - * @deprecated Import from `@backstage/plugin-scaffolder-node` instead. - */ -export type TaskStatus = _TaskStatus; - -/** - * The state of a completed task. - * - * @public - * @deprecated Import from `@backstage/plugin-scaffolder-node` instead. - */ -export type TaskCompletionState = _TaskCompletionState; - -/** - * SerializedTask - * - * @public - * @deprecated Import from `@backstage/plugin-scaffolder-node` instead. - */ -export type SerializedTask = _SerializedTask; - -/** - * TaskEventType - * - * @public - * @deprecated Import from `@backstage/plugin-scaffolder-node` instead. - */ -export type TaskEventType = _TaskEventType; - -/** - * SerializedTaskEvent - * - * @public - * @deprecated Import from `@backstage/plugin-scaffolder-node` instead. - */ -export type SerializedTaskEvent = _SerializedTaskEvent; - -/** - * The result of `TaskBroker.dispatch`. - * - * @public - * @deprecated Import from `@backstage/plugin-scaffolder-node` instead. - */ -export type TaskBrokerDispatchResult = _TaskBrokerDispatchResult; - -/** - * The options passed to `TaskBroker.dispatch`. - * Currently a spec and optional secrets - * - * @public - * @deprecated Import from `@backstage/plugin-scaffolder-node` instead. - */ -export type TaskBrokerDispatchOptions = _TaskBrokerDispatchOptions; - -/** - * Task - * - * @public - * @deprecated Import from `@backstage/plugin-scaffolder-node` instead. - */ -export type TaskContext = _TaskContext; - -/** - * TaskBroker - * - * @public - * @deprecated Import from `@backstage/plugin-scaffolder-node` instead. - */ -export type TaskBroker = _TaskBroker; - /** * TaskStoreEmitOptions * * @public + * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. */ export type TaskStoreEmitOptions = { taskId: string; @@ -117,6 +40,7 @@ export type TaskStoreEmitOptions = { * TaskStoreListEventsOptions * * @public + * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. */ export type TaskStoreListEventsOptions = { isTaskRecoverable?: boolean; @@ -128,6 +52,7 @@ export type TaskStoreListEventsOptions = { * TaskStoreShutDownTaskOptions * * @public + * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. */ export type TaskStoreShutDownTaskOptions = { taskId: string; @@ -136,6 +61,7 @@ export type TaskStoreShutDownTaskOptions = { /** * The options passed to {@link TaskStore.createTask} * @public + * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. */ export type TaskStoreCreateTaskOptions = { spec: TaskSpec; @@ -146,6 +72,7 @@ export type TaskStoreCreateTaskOptions = { /** * The options passed to {@link TaskStore.recoverTasks} * @public + * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. */ export type TaskStoreRecoverTaskOptions = { timeout: HumanDuration; @@ -154,6 +81,7 @@ export type TaskStoreRecoverTaskOptions = { /** * The response from {@link TaskStore.createTask} * @public + * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. */ export type TaskStoreCreateTaskResult = { taskId: string; @@ -163,6 +91,7 @@ export type TaskStoreCreateTaskResult = { * TaskStore * * @public + * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. */ export interface TaskStore { cancelTask?(options: TaskStoreEmitOptions): Promise; diff --git a/plugins/scaffolder-backend/src/service/conditionExports.ts b/plugins/scaffolder-backend/src/service/alpha.ts similarity index 100% rename from plugins/scaffolder-backend/src/service/conditionExports.ts rename to plugins/scaffolder-backend/src/service/alpha.ts diff --git a/plugins/scaffolder-backend/src/service/helpers.ts b/plugins/scaffolder-backend/src/service/helpers.ts index cd55068d98..e29eec4d64 100644 --- a/plugins/scaffolder-backend/src/service/helpers.ts +++ b/plugins/scaffolder-backend/src/service/helpers.ts @@ -14,7 +14,10 @@ * limitations under the License. */ -import { CatalogApi } from '@backstage/catalog-client'; +import { + BackstageCredentials, + LoggerService, +} from '@backstage/backend-plugin-api'; import { ANNOTATION_LOCATION, ANNOTATION_SOURCE_LOCATION, @@ -25,14 +28,14 @@ import { } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { assertError, InputError, NotFoundError } from '@backstage/errors'; +import { CatalogService } from '@backstage/plugin-catalog-node'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import fs from 'fs-extra'; import os from 'os'; -import { Logger } from 'winston'; export async function getWorkingDirectory( config: Config, - logger: Logger, + logger: LoggerService, ): Promise { if (!config.has('backend.workingDirectory')) { return os.tmpdir(); @@ -89,16 +92,18 @@ export function getEntityBaseUrl(entity: Entity): string | undefined { */ export async function findTemplate(options: { entityRef: CompoundEntityRef; - token?: string; - catalogApi: CatalogApi; + catalog: CatalogService; + credentials: BackstageCredentials; }): Promise { - const { entityRef, token, catalogApi } = options; + const { entityRef, catalog, credentials } = options; if (entityRef.kind.toLocaleLowerCase('en-US') !== 'template') { throw new InputError(`Invalid kind, only 'Template' kind is supported`); } - const template = await catalogApi.getEntityByRef(entityRef, { token }); + const template = await catalog.getEntityByRef(entityRef, { + credentials, + }); if (!template) { throw new NotFoundError( `Template ${stringifyEntityRef(entityRef)} not found`, diff --git a/plugins/scaffolder-backend/src/service/permissions.ts b/plugins/scaffolder-backend/src/service/permissions.ts new file mode 100644 index 0000000000..9b12e115b9 --- /dev/null +++ b/plugins/scaffolder-backend/src/service/permissions.ts @@ -0,0 +1,61 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { PermissionRule } from '@backstage/plugin-permission-node'; +import { + TemplateEntityStepV1beta3, + TemplateParametersV1beta3, +} from '@backstage/plugin-scaffolder-common'; +import { + RESOURCE_TYPE_SCAFFOLDER_ACTION, + RESOURCE_TYPE_SCAFFOLDER_TEMPLATE, +} from '@backstage/plugin-scaffolder-common/alpha'; +import { PermissionRuleParams } from '@backstage/plugin-permission-common'; + +/** + * @public + */ +export type TemplatePermissionRuleInput< + TParams extends PermissionRuleParams = PermissionRuleParams, +> = PermissionRule< + TemplateEntityStepV1beta3 | TemplateParametersV1beta3, + {}, + typeof RESOURCE_TYPE_SCAFFOLDER_TEMPLATE, + TParams +>; +export function isTemplatePermissionRuleInput( + permissionRule: TemplatePermissionRuleInput | ActionPermissionRuleInput, +): permissionRule is TemplatePermissionRuleInput { + return permissionRule.resourceType === RESOURCE_TYPE_SCAFFOLDER_TEMPLATE; +} + +/** + * + * @public + */ +export type ActionPermissionRuleInput< + TParams extends PermissionRuleParams = PermissionRuleParams, +> = PermissionRule< + TemplateEntityStepV1beta3 | TemplateParametersV1beta3, + {}, + typeof RESOURCE_TYPE_SCAFFOLDER_ACTION, + TParams +>; +export function isActionPermissionRuleInput( + permissionRule: TemplatePermissionRuleInput | ActionPermissionRuleInput, +): permissionRule is ActionPermissionRuleInput { + return permissionRule.resourceType === RESOURCE_TYPE_SCAFFOLDER_ACTION; +} diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index d26cc955b3..04f619fd02 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -26,14 +26,18 @@ import ObservableImpl from 'zen-observable'; * Due to a circular dependency between this plugin and the * plugin-scaffolder-backend-module-cookiecutter plugin, it results in an error: * TypeError: _pluginscaffolderbackend.createTemplateAction is not a function + * + * TODO: These tests need refactoring. Seems like the identityApi tests don't do anything different anymore. + * And there's very little value re-reunning all the tests again with just additional template filters and values. + * Let's break them out into better tests. Didn't want to do it in the same PR i'm working on right now. */ import { parseEntityRef, stringifyEntityRef, UserEntity, } from '@backstage/catalog-model'; -import { createRouter, DatabaseTaskStore } from '../index'; import { + createTemplateAction, TaskBroker, TemplateFilter, TemplateGlobal, @@ -54,7 +58,6 @@ import { createTemplateGlobalFunction, createTemplateGlobalValue, } from '@backstage/plugin-scaffolder-node/alpha'; -import { UrlReaders } from '@backstage/backend-defaults/urlReader'; import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; import { EventsService } from '@backstage/plugin-events-node'; import { DatabaseService } from '@backstage/backend-plugin-api'; @@ -66,6 +69,8 @@ import { extractGlobalValueMetadata, } from '../util/templating'; import { createDefaultFilters } from '../lib/templating/filters/createDefaultFilters'; +import { createRouter } from './router'; +import { DatabaseTaskStore } from '../scaffolder/tasks/DatabaseTaskStore'; const mockAccess = jest.fn(); @@ -95,11 +100,6 @@ function createDatabase(): DatabaseService { ).forPlugin('scaffolder'); } -const mockUrlReader = UrlReaders.default({ - logger: mockServices.logger.mock(), - config: new ConfigReader({}), -}); - const config = new ConfigReader({}); describe.each([ @@ -187,23 +187,18 @@ describe.each([ let app: express.Express; let loggerSpy: jest.SpyInstance; let taskBroker: TaskBroker; - const catalogClient = catalogServiceMock.mock(); + const catalogMock = catalogServiceMock.mock(); const permissionApi = { authorize: jest.fn(), authorizeConditional: jest.fn(), } as unknown as PermissionEvaluator; const auth = mockServices.auth(); const httpAuth = mockServices.httpAuth(); - const discovery = mockServices.discovery(); const events = { publish: jest.fn(), } as unknown as EventsService; const credentials = mockCredentials.user(); - const token = mockCredentials.service.token({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); const getMockTemplate = (): TemplateEntityV1beta3 => ({ apiVersion: 'scaffolder.backstage.io/v1beta3', @@ -303,20 +298,31 @@ describe.each([ logger: logger, config: new ConfigReader({}), database: createDatabase(), - catalogClient, - reader: mockUrlReader, + catalog: catalogMock, taskBroker, permissions: permissionApi, auth, httpAuth, - discovery, events, additionalTemplateFilters, additionalTemplateGlobals, + actions: [ + createTemplateAction({ + id: 'test', + description: 'test', + schema: { + input: z => + z.object({ + test: z.string(), + }), + }, + handler: async () => {}, + }), + ], }); app = express().use(router); - catalogClient.getEntityByRef.mockImplementation(async ref => { + catalogMock.getEntityByRef.mockImplementation(async ref => { const { kind } = parseEntityRef(ref); if (kind.toLocaleLowerCase() === 'template') { @@ -356,7 +362,7 @@ describe.each([ const response = await request(app).get('/v2/actions').send(); expect(response.status).toEqual(200); expect(response.body[0].id).toBeDefined(); - expect(response.body.length).toBeGreaterThan(8); + expect(response.body.length).toBe(1); }); }); @@ -448,7 +454,6 @@ describe.each([ expect.objectContaining({ createdBy: 'user:default/mock', secrets: { - backstageToken: token, __initiatorCredentials: JSON.stringify(credentials), }, @@ -600,7 +605,6 @@ describe.each([ status: 'completed', createdAt: '', secrets: { - backstageToken: token, __initiatorCredentials: JSON.stringify(credentials), }, createdBy: '', @@ -852,9 +856,9 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ directoryContents: [], }); - expect(catalogClient.getEntityByRef).toHaveBeenCalledTimes(1); + expect(catalogMock.getEntityByRef).toHaveBeenCalledTimes(1); - expect(catalogClient.getEntityByRef).toHaveBeenCalledWith( + expect(catalogMock.getEntityByRef).toHaveBeenCalledWith( 'user:default/mock', expect.anything(), ); @@ -880,17 +884,28 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ logger: logger, config: new ConfigReader({}), database: createDatabase(), - catalogClient, - reader: mockUrlReader, + catalog: catalogMock, taskBroker, permissions: permissionApi, auth, httpAuth, - discovery, + actions: [ + createTemplateAction({ + id: 'test', + description: 'test', + schema: { + input: z => + z.object({ + test: z.string(), + }), + }, + handler: async () => {}, + }), + ], }); app = express().use(router); - catalogClient.getEntityByRef.mockImplementation(async ref => { + catalogMock.getEntityByRef.mockImplementation(async ref => { const { kind } = parseEntityRef(ref); if (kind.toLocaleLowerCase() === 'template') { @@ -929,7 +944,7 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ const response = await request(app).get('/v2/actions').send(); expect(response.status).toEqual(200); expect(response.body[0].id).toBeDefined(); - expect(response.body.length).toBeGreaterThan(8); + expect(response.body.length).toBe(1); }); }); @@ -1101,7 +1116,6 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ expect.objectContaining({ createdBy: 'user:default/mock', secrets: { - backstageToken: token, __initiatorCredentials: JSON.stringify(credentials), }, @@ -1171,7 +1185,6 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ expect.objectContaining({ createdBy: 'user:default/mock', secrets: { - backstageToken: token, __initiatorCredentials: JSON.stringify(credentials), }, @@ -1260,7 +1273,6 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ expect.objectContaining({ createdBy: 'user:default/mock', secrets: { - backstageToken: token, __initiatorCredentials: JSON.stringify(credentials), }, @@ -1404,7 +1416,6 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ status: 'completed', createdAt: '', secrets: { - backstageToken: token, __initiatorCredentials: JSON.stringify(credentials), }, createdBy: '', @@ -1651,13 +1662,11 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ logger: loggerToWinstonLogger(mockServices.logger.mock()), config: new ConfigReader({}), database: createDatabase(), - catalogClient, - reader: mockUrlReader, + catalog: catalogMock, taskBroker, permissions: permissionApi, auth, httpAuth, - discovery, autocompleteHandlers: { 'test-provider': handleAutocompleteRequest, }, diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index f2e323e831..0ea6293f36 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -14,21 +14,18 @@ * limitations under the License. */ -import { createLegacyAuthAdapters } from '@backstage/backend-common'; import { AuditorService, AuthService, BackstageCredentials, DatabaseService, - DiscoveryService, HttpAuthService, LifecycleService, + LoggerService, PermissionsService, resolveSafeChildPath, SchedulerService, - UrlReaderService, } from '@backstage/backend-plugin-api'; -import { CatalogApi } from '@backstage/catalog-client'; import { CompoundEntityRef, Entity, @@ -37,25 +34,19 @@ import { UserEntity, } from '@backstage/catalog-model'; import { Config, readDurationFromConfig } from '@backstage/config'; -import { InputError, NotFoundError, stringifyError } from '@backstage/errors'; +import { InputError, NotFoundError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; -import { - IdentityApi, - IdentityApiGetIdentityRequest, -} from '@backstage/plugin-auth-node'; + import { EventsService } from '@backstage/plugin-events-node'; -import { PermissionRuleParams } from '@backstage/plugin-permission-common'; + import { createConditionAuthorizer, createPermissionIntegrationRouter, - PermissionRule, } from '@backstage/plugin-permission-node'; import { TaskSpec, - TemplateEntityStepV1beta3, TemplateEntityV1beta3, templateEntityV1beta3Validator, - TemplateParametersV1beta3, } from '@backstage/plugin-scaffolder-common'; import { RESOURCE_TYPE_SCAFFOLDER_ACTION, @@ -82,17 +73,15 @@ import { CreatedTemplateGlobal, WorkspaceProvider, } from '@backstage/plugin-scaffolder-node/alpha'; -import { HumanDuration, JsonObject, JsonValue } from '@backstage/types'; +import { HumanDuration, JsonObject } from '@backstage/types'; import express from 'express'; import Router from 'express-promise-router'; import { validate } from 'jsonschema'; import { Duration } from 'luxon'; import { pathToFileURL } from 'url'; import { v4 as uuid } from 'uuid'; -import { Logger } from 'winston'; import { z } from 'zod'; import { - createBuiltinActions, DatabaseTaskStore, TaskWorker, TemplateActionRegistry, @@ -109,7 +98,6 @@ import { parseStringsParam, } from './helpers'; import { scaffolderActionRules, scaffolderTemplateRules } from './rules'; -import { HostDiscovery } from '@backstage/backend-defaults/discovery'; import { convertFiltersToRecord, convertGlobalsToRecord, @@ -118,63 +106,25 @@ import { extractGlobalValueMetadata, } from '../util/templating'; import { createDefaultFilters } from '../lib/templating/filters/createDefaultFilters'; - -/** - * - * @public - */ -export type TemplatePermissionRuleInput< - TParams extends PermissionRuleParams = PermissionRuleParams, -> = PermissionRule< - TemplateEntityStepV1beta3 | TemplateParametersV1beta3, - {}, - typeof RESOURCE_TYPE_SCAFFOLDER_TEMPLATE, - TParams ->; -function isTemplatePermissionRuleInput( - permissionRule: TemplatePermissionRuleInput | ActionPermissionRuleInput, -): permissionRule is TemplatePermissionRuleInput { - return permissionRule.resourceType === RESOURCE_TYPE_SCAFFOLDER_TEMPLATE; -} - -/** - * - * @public - */ -export type ActionPermissionRuleInput< - TParams extends PermissionRuleParams = PermissionRuleParams, -> = PermissionRule< - TemplateEntityStepV1beta3 | TemplateParametersV1beta3, - {}, - typeof RESOURCE_TYPE_SCAFFOLDER_ACTION, - TParams ->; -function isActionPermissionRuleInput( - permissionRule: TemplatePermissionRuleInput | ActionPermissionRuleInput, -): permissionRule is ActionPermissionRuleInput { - return permissionRule.resourceType === RESOURCE_TYPE_SCAFFOLDER_ACTION; -} +import { + ActionPermissionRuleInput, + isActionPermissionRuleInput, + isTemplatePermissionRuleInput, + TemplatePermissionRuleInput, +} from './permissions'; +import { CatalogService } from '@backstage/plugin-catalog-node'; /** * RouterOptions - * - * @public - * @deprecated Please migrate to the new backend system as this will be removed in the future. */ export interface RouterOptions { - logger: Logger; + logger: LoggerService; config: Config; - reader: UrlReaderService; lifecycle?: LifecycleService; database: DatabaseService; - catalogClient: CatalogApi; + catalog: CatalogService; scheduler?: SchedulerService; actions?: TemplateAction[]; - /** - * @deprecated taskWorkers is deprecated in favor of concurrentTasksLimit option with a single TaskWorker - * @defaultValue 1 - */ - taskWorkers?: number; /** * Sets the number of concurrent tasks that can be run at any given time on the TaskWorker * @defaultValue 10 @@ -192,10 +142,8 @@ export interface RouterOptions { permissionRules?: Array< TemplatePermissionRuleInput | ActionPermissionRuleInput >; - auth?: AuthService; - httpAuth?: HttpAuthService; - identity?: IdentityApi; - discovery?: DiscoveryService; + auth: AuthService; + httpAuth: HttpAuthService; events?: EventsService; auditor?: AuditorService; autocompleteHandlers?: Record; @@ -205,70 +153,6 @@ function isSupportedTemplate(entity: TemplateEntityV1beta3) { return entity.apiVersion === 'scaffolder.backstage.io/v1beta3'; } -/* - * @deprecated This function remains as the DefaultIdentityClient behaves slightly differently to the pre-existing - * scaffolder behaviour. Specifically if the token fails to parse, the DefaultIdentityClient will raise an error. - * The scaffolder did not raise an error in this case. As such we chose to allow it to behave as it did previously - * until someone explicitly passes an IdentityApi. When we have reasonable confidence that most backstage deployments - * are using the IdentityApi, we can remove this function. - */ -function buildDefaultIdentityClient(options: RouterOptions): IdentityApi { - return { - getIdentity: async ({ request }: IdentityApiGetIdentityRequest) => { - const header = request.headers.authorization; - const { logger } = options; - - if (!header) { - return undefined; - } - - try { - const token = header.match(/^Bearer\s(\S+\.\S+\.\S+)$/i)?.[1]; - if (!token) { - throw new TypeError('Expected Bearer with JWT'); - } - - const [_header, rawPayload, _signature] = token.split('.'); - const payload: JsonValue = JSON.parse( - Buffer.from(rawPayload, 'base64').toString(), - ); - - if ( - typeof payload !== 'object' || - payload === null || - Array.isArray(payload) - ) { - throw new TypeError('Malformed JWT payload'); - } - - const sub = payload.sub; - if (typeof sub !== 'string') { - throw new TypeError('Expected string sub claim'); - } - - if (sub === 'backstage-server') { - return undefined; - } - - // Check that it's a valid ref, otherwise this will throw. - parseEntityRef(sub); - - return { - identity: { - userEntityRef: sub, - ownershipEntityRefs: [], - type: 'user', - }, - token, - }; - } catch (e) { - logger.error(`Invalid authorization header: ${stringifyError(e)}`); - return undefined; - } - }, - }; -} - const readDuration = ( config: Config, key: string, @@ -282,8 +166,6 @@ const readDuration = ( /** * A method to create a router for the scaffolder backend plugin. - * @public - * @deprecated Please migrate to the new backend system as this will be removed in the future. */ export async function createRouter( options: RouterOptions, @@ -295,30 +177,22 @@ export async function createRouter( const { logger: parentLogger, config, - reader, database, - catalogClient, + catalog, actions, - taskWorkers, scheduler, additionalTemplateFilters, additionalTemplateGlobals, additionalWorkspaceProviders, permissions, permissionRules, - discovery = HostDiscovery.fromConfig(config), - identity = buildDefaultIdentityClient(options), autocompleteHandlers = {}, events: eventsService, + auth, + httpAuth, auditor, } = options; - const { auth, httpAuth } = createLegacyAuthAdapters({ - ...options, - identity, - discovery, - }); - const concurrentTasksLimit = options.concurrentTasksLimit ?? options.config.getOptionalNumber('scaffolder.concurrentTasksLimit'); @@ -391,35 +265,23 @@ export async function createRouter( 'scaffolder.EXPERIMENTAL_gracefulShutdown', ); - for (let i = 0; i < (taskWorkers || 1); i++) { - const worker = await TaskWorker.create({ - taskBroker, - actionRegistry, - integrations, - logger, - auditor, - workingDirectory, - concurrentTasksLimit, - permissions, - gracefulShutdown, - ...templateExtensions, - }); - workers.push(worker); - } + const worker = await TaskWorker.create({ + taskBroker, + actionRegistry, + integrations, + logger, + auditor, + workingDirectory, + concurrentTasksLimit, + permissions, + gracefulShutdown, + ...templateExtensions, + }); + + workers.push(worker); } - const actionsToRegister = Array.isArray(actions) - ? actions - : createBuiltinActions({ - integrations, - catalogClient, - reader, - config, - auth, - ...templateExtensions, - }); - - actionsToRegister.forEach(action => actionRegistry.register(action)); + actions?.forEach(action => actionRegistry.register(action)); const launchWorkers = () => workers.forEach(worker => worker.start()); @@ -493,16 +355,7 @@ export async function createRouter( try { const credentials = await httpAuth.credentials(req); - const { token } = await auth.getPluginRequestToken({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); - - const template = await authorizeTemplate( - req.params, - token, - credentials, - ); + const template = await authorizeTemplate(req.params, credentials); const parameters = [template.spec.parameters ?? []].flat(); @@ -581,17 +434,12 @@ export async function createRouter( permissionService: permissions, }); - const { token } = await auth.getPluginRequestToken({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); - const userEntityRef = auth.isPrincipal(credentials, 'user') ? credentials.principal.userEntityRef : undefined; const userEntity = userEntityRef - ? await catalogClient.getEntityByRef(userEntityRef, { token }) + ? await catalog.getEntityByRef(userEntityRef, { credentials }) : undefined; let auditLog = `Scaffolding task for ${templateRef}`; @@ -604,7 +452,6 @@ export async function createRouter( const template = await authorizeTemplate( { kind, namespace, name }, - token, credentials, ); @@ -652,7 +499,7 @@ export async function createRouter( const secrets: InternalTaskSecrets = { ...req.body.secrets, - backstageToken: token, + backstageToken: (credentials as any).token, __initiatorCredentials: JSON.stringify({ ...credentials, // credentials.token is nonenumerable and will not be serialized, so we need to add it explicitly @@ -1010,17 +857,12 @@ export async function createRouter( throw new InputError('Input template is not a template'); } - const { token } = await auth.getPluginRequestToken({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); - const userEntityRef = auth.isPrincipal(credentials, 'user') ? credentials.principal.userEntityRef : undefined; const userEntity = userEntityRef - ? await catalogClient.getEntityByRef(userEntityRef, { token }) + ? await catalog.getEntityByRef(userEntityRef, { credentials }) : undefined; const templateRef: string = `${template.kind}:${ @@ -1086,7 +928,7 @@ export async function createRouter( })), secrets: { ...body.secrets, - ...(token && { backstageToken: token }), + backstageToken: (credentials as any).token, }, credentials, }); @@ -1149,13 +991,12 @@ export async function createRouter( async function authorizeTemplate( entityRef: CompoundEntityRef, - token: string | undefined, credentials: BackstageCredentials, ) { const template = await findTemplate({ - catalogApi: catalogClient, + catalog, entityRef, - token, + credentials, }); if (!isSupportedTemplate(template)) { diff --git a/plugins/scaffolder-node-test-utils/CHANGELOG.md b/plugins/scaffolder-node-test-utils/CHANGELOG.md index aae5de362b..6eddc2ddb1 100644 --- a/plugins/scaffolder-node-test-utils/CHANGELOG.md +++ b/plugins/scaffolder-node-test-utils/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-scaffolder-node-test-utils +## 0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/backend-test-utils@1.6.0-next.1 + - @backstage/types@1.2.1 + +## 0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.3-next.0 + - @backstage/backend-test-utils@1.6.0-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + ## 0.2.2 ### Patch Changes diff --git a/plugins/scaffolder-node-test-utils/package.json b/plugins/scaffolder-node-test-utils/package.json index e3a8c4473c..8f334cd23f 100644 --- a/plugins/scaffolder-node-test-utils/package.json +++ b/plugins/scaffolder-node-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-node-test-utils", - "version": "0.2.2", + "version": "0.2.3-next.1", "backstage": { "role": "node-library", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-node-test-utils/report.api.md b/plugins/scaffolder-node-test-utils/report.api.md index b95d4cef0c..2780baf1d5 100644 --- a/plugins/scaffolder-node-test-utils/report.api.md +++ b/plugins/scaffolder-node-test-utils/report.api.md @@ -7,12 +7,12 @@ import { ActionContext } from '@backstage/plugin-scaffolder-node'; import { JsonObject } from '@backstage/types'; // @public -export const createMockActionContext: < +export function createMockActionContext< TActionInput extends JsonObject = JsonObject, - TActionOutput extends JsonObject = JsonObject, + TActionOutput extends JsonObject = any, >( options?: Partial>, -) => ActionContext; +): ActionContext; // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts b/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts index bb6e3917f1..a6703a12d1 100644 --- a/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts +++ b/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts @@ -30,13 +30,14 @@ import { loggerToWinstonLogger } from './loggerToWinstonLogger'; * @public * @param options - optional parameters to override default mock context */ -export const createMockActionContext = < +export function createMockActionContext< TActionInput extends JsonObject = JsonObject, - TActionOutput extends JsonObject = JsonObject, + TActionOutput extends JsonObject = any, >( options?: Partial>, -): ActionContext => { +): ActionContext { const credentials = mockCredentials.user(); + const defaultContext = { logger: loggerToWinstonLogger(mockServices.logger.mock()), logStream: new PassThrough(), @@ -66,16 +67,8 @@ export const createMockActionContext = < }; } - const { - input, - logger, - logStream, - secrets, - templateInfo, - workspacePath, - task, - user, - } = options; + const { input, logger, secrets, templateInfo, workspacePath, task, user } = + options; return { ...defaultContext, @@ -84,11 +77,10 @@ export const createMockActionContext = < createTemporaryDirectory: jest.fn().mockResolvedValue(workspacePath), }), ...(logger && { logger }), - ...(logStream && { logStream }), ...(input && { input }), ...(secrets && { secrets }), ...(task && { task }), ...(user && { user }), templateInfo, }; -}; +} diff --git a/plugins/scaffolder-node/CHANGELOG.md b/plugins/scaffolder-node/CHANGELOG.md index 2877fbbc84..068a613888 100644 --- a/plugins/scaffolder-node/CHANGELOG.md +++ b/plugins/scaffolder-node/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-scaffolder-node +## 0.8.3-next.1 + +### Patch Changes + +- e89d7b6: Use `LoggerService` instead of `Logger`. This is a non-breaking change, as the `LoggerService` is a subset of the `Logger` interface. +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/types@1.2.1 + - @backstage/plugin-scaffolder-common@1.5.11 + +## 0.8.3-next.0 + +### Patch Changes + +- 9c8ff0c: Update pull request creation filter to include .gitignore files in the created pull request +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + ## 0.8.2 ### Patch Changes diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json index 5385d06849..6e44babf81 100644 --- a/plugins/scaffolder-node/package.json +++ b/plugins/scaffolder-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-node", - "version": "0.8.2", + "version": "0.8.3-next.1", "description": "The plugin-scaffolder-node module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "node-library", diff --git a/plugins/scaffolder-node/report.api.md b/plugins/scaffolder-node/report.api.md index f2e8501527..a23a9137b1 100644 --- a/plugins/scaffolder-node/report.api.md +++ b/plugins/scaffolder-node/report.api.md @@ -7,7 +7,6 @@ import { BackstageCredentials } from '@backstage/backend-plugin-api'; import { Expand } from '@backstage/types'; import { JsonObject } from '@backstage/types'; import { JsonValue } from '@backstage/types'; -import { Logger } from 'winston'; import { LoggerService } from '@backstage/backend-plugin-api'; import { Observable } from '@backstage/types'; import { Schema } from 'jsonschema'; @@ -25,63 +24,34 @@ import { z } from 'zod'; export type ActionContext< TActionInput extends JsonObject, TActionOutput extends JsonObject = JsonObject, - TSchemaType extends 'v1' | 'v2' = 'v1', -> = TSchemaType extends 'v2' - ? { - logger: LoggerService; - secrets?: TaskSecrets; - workspacePath: string; - input: TActionInput; - checkpoint(opts: { - key: string; - fn: () => Promise | T; - }): Promise; - output( - name: keyof TActionOutput, - value: TActionOutput[keyof TActionOutput], - ): void; - createTemporaryDirectory(): Promise; - getInitiatorCredentials(): Promise; - task: { - id: string; - }; - templateInfo?: TemplateInfo; - isDryRun?: boolean; - user?: { - entity?: UserEntity; - ref?: string; - }; - signal?: AbortSignal; - each?: JsonObject; - } - : { - logger: Logger; - logStream: Writable; - secrets?: TaskSecrets; - workspacePath: string; - input: TActionInput; - checkpoint(opts: { - key: string; - fn: () => Promise | T; - }): Promise; - output( - name: keyof TActionOutput, - value: TActionOutput[keyof TActionOutput], - ): void; - createTemporaryDirectory(): Promise; - getInitiatorCredentials(): Promise; - task: { - id: string; - }; - templateInfo?: TemplateInfo; - isDryRun?: boolean; - user?: { - entity?: UserEntity; - ref?: string; - }; - signal?: AbortSignal; - each?: JsonObject; - }; + _TSchemaType extends 'v2' = 'v2', +> = { + logger: LoggerService; + secrets?: TaskSecrets; + workspacePath: string; + input: TActionInput; + checkpoint(opts: { + key: string; + fn: () => Promise | T; + }): Promise; + output( + name: keyof TActionOutput, + value: TActionOutput[keyof TActionOutput], + ): void; + createTemporaryDirectory(): Promise; + getInitiatorCredentials(): Promise; + task: { + id: string; + }; + templateInfo?: TemplateInfo; + isDryRun?: boolean; + user?: { + entity?: UserEntity; + ref?: string; + }; + signal?: AbortSignal; + each?: JsonObject; +}; // @public (undocumented) export function addFiles(options: { @@ -95,7 +65,7 @@ export function addFiles(options: { | { token: string; }; - logger?: Logger | undefined; + logger?: LoggerService | undefined; }): Promise; // @public (undocumented) @@ -110,7 +80,7 @@ export function cloneRepo(options: { | { token: string; }; - logger?: Logger | undefined; + logger?: LoggerService | undefined; ref?: string | undefined; depth?: number | undefined; noCheckout?: boolean | undefined; @@ -127,7 +97,7 @@ export function commitAndPushBranch(options: { | { token: string; }; - logger?: Logger | undefined; + logger?: LoggerService | undefined; commitMessage: string; gitAuthorInfo?: { name?: string; @@ -152,7 +122,7 @@ export function commitAndPushRepo(input: { | { token: string; }; - logger: Logger; + logger: LoggerService; commitMessage: string; gitAuthorInfo?: { name?: string; @@ -177,72 +147,70 @@ export function createBranch(options: { | { token: string; }; - logger?: Logger | undefined; + logger?: LoggerService | undefined; }): Promise; -// @public @deprecated (undocumented) -export function createTemplateAction< - TInputParams extends JsonObject = JsonObject, - TOutputParams extends JsonObject = JsonObject, - TInputSchema extends JsonObject = JsonObject, - TOutputSchema extends JsonObject = JsonObject, - TActionInput extends JsonObject = TInputParams, - TActionOutput extends JsonObject = TOutputParams, ->( - action: TemplateActionOptions< - TActionInput, - TActionOutput, - TInputSchema, - TOutputSchema, - 'v1' - >, -): TemplateAction; - -// @public @deprecated (undocumented) -export function createTemplateAction< - TInputParams extends JsonObject = JsonObject, - TOutputParams extends JsonObject = JsonObject, - TInputSchema extends z.ZodType = z.ZodType, - TOutputSchema extends z.ZodType = z.ZodType, - TActionInput extends JsonObject = z.infer, - TActionOutput extends JsonObject = z.infer, ->( - action: TemplateActionOptions< - TActionInput, - TActionOutput, - TInputSchema, - TOutputSchema, - 'v1' - >, -): TemplateAction; - // @public export function createTemplateAction< - TInputSchema extends { - [key in string]: (zImpl: typeof z) => z.ZodType; - }, - TOutputSchema extends { - [key in string]: (zImpl: typeof z) => z.ZodType; - }, + TInputSchema extends + | { + [key in string]: (zImpl: typeof z) => z.ZodType; + } + | ((zImpl: typeof z) => z.ZodType), + TOutputSchema extends + | { + [key in string]: (zImpl: typeof z) => z.ZodType; + } + | ((zImpl: typeof z) => z.ZodType), >( action: TemplateActionOptions< - { - [key in keyof TInputSchema]: z.infer>; - }, - { - [key in keyof TOutputSchema]: z.infer>; - }, + TInputSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + } + ? { + [key in keyof TInputSchema]: z.infer>; + } + : TInputSchema extends (zImpl: typeof z) => z.ZodType + ? z.infer> + : never, + TOutputSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + } + ? { + [key in keyof TOutputSchema]: z.infer>; + } + : TOutputSchema extends (zImpl: typeof z) => z.ZodType + ? z.infer> + : never, TInputSchema, TOutputSchema, 'v2' >, ): TemplateAction< - FlattenOptionalProperties<{ - [key in keyof TInputSchema]: z.output>; - }>, - FlattenOptionalProperties<{ - [key in keyof TOutputSchema]: z.output>; - }>, + FlattenOptionalProperties< + TInputSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + } + ? { + [key in keyof TInputSchema]: z.output>; + } + : TInputSchema extends (zImpl: typeof z) => z.ZodType + ? z.output> + : never + >, + FlattenOptionalProperties< + TOutputSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + } + ? { + [key in keyof TOutputSchema]: z.output< + ReturnType + >; + } + : TOutputSchema extends (zImpl: typeof z) => z.ZodType + ? z.output> + : never + >, 'v2' >; @@ -304,7 +272,7 @@ export function initRepoAndPush(input: { | { token: string; }; - logger: Logger; + logger: LoggerService; defaultBranch?: string; commitMessage?: string; gitAuthorInfo?: { @@ -316,6 +284,9 @@ export function initRepoAndPush(input: { commitHash: string; }>; +// @public +export function isNotGitDirectoryOrContents(path: string): boolean; + // @public (undocumented) export const parseRepoUrl: ( repoUrl: string, @@ -510,7 +481,7 @@ export type TaskStatus = export type TemplateAction< TActionInput extends JsonObject = JsonObject, TActionOutput extends JsonObject = JsonObject, - TSchemaType extends 'v1' | 'v2' = 'v1', + TSchemaType extends 'v2' = 'v2', > = { id: string; description?: string; @@ -533,18 +504,20 @@ export type TemplateActionOptions< TActionInput extends JsonObject = {}, TActionOutput extends JsonObject = {}, TInputSchema extends - | JsonObject - | z.ZodType | { [key in string]: (zImpl: typeof z) => z.ZodType; - } = JsonObject, + } + | ((zImpl: typeof z) => z.ZodType) = { + [key in string]: (zImpl: typeof z) => z.ZodType; + }, TOutputSchema extends - | JsonObject - | z.ZodType | { [key in string]: (zImpl: typeof z) => z.ZodType; - } = JsonObject, - TSchemaType extends 'v1' | 'v2' = 'v1' | 'v2', + } + | ((zImpl: typeof z) => z.ZodType) = { + [key in string]: (zImpl: typeof z) => z.ZodType; + }, + TSchemaType extends 'v2' = 'v2', > = { id: string; description?: string; diff --git a/plugins/scaffolder-node/src/actions/createTemplateAction.test.ts b/plugins/scaffolder-node/src/actions/createTemplateAction.test.ts index 43d5d2f660..b4ed095f03 100644 --- a/plugins/scaffolder-node/src/actions/createTemplateAction.test.ts +++ b/plugins/scaffolder-node/src/actions/createTemplateAction.test.ts @@ -14,85 +14,8 @@ * limitations under the License. */ import { createTemplateAction } from './createTemplateAction'; -import { z } from 'zod'; describe('createTemplateAction', () => { - it('should allow creating with jsonschema and use the old deprecated types', () => { - const action = createTemplateAction<{ repoUrl: string }, { test: string }>({ - id: 'test', - schema: { - input: { - type: 'object', - required: ['repoUrl'], - properties: { - repoUrl: { type: 'string' }, - }, - }, - output: { - type: 'object', - required: ['test'], - properties: { - test: { type: 'string' }, - }, - }, - }, - handler: async ctx => { - // @ts-expect-error - repoUrl is string - const a: number = ctx.input.repoUrl; - - const b: string = ctx.input.repoUrl; - expect(b).toBeDefined(); - - const stream = ctx.logStream; - expect(stream).toBeDefined(); - - ctx.output('test', 'value'); - - // @ts-expect-error - not valid output type - ctx.output('test', 4); - - // @ts-expect-error - not valid output name - ctx.output('test2', 'value'); - }, - }); - - expect(action).toBeDefined(); - }); - - it('should allow creating with zod and use the old deprecated types', () => { - const action = createTemplateAction({ - id: 'test', - schema: { - input: z.object({ - repoUrl: z.string(), - }), - output: z.object({ - test: z.string(), - }), - }, - handler: async ctx => { - // @ts-expect-error - repoUrl is string - const a: number = ctx.input.repoUrl; - - const b: string = ctx.input.repoUrl; - expect(b).toBeDefined(); - - const stream = ctx.logStream; - expect(stream).toBeDefined(); - - ctx.output('test', 'value'); - - // @ts-expect-error - not valid output type - ctx.output('test', 4); - - // @ts-expect-error - not valid output name - ctx.output('test2', 'value'); - }, - }); - - expect(action).toBeDefined(); - }); - it('should allow creating with new first class zod support', () => { const action = createTemplateAction({ id: 'test', @@ -107,14 +30,10 @@ describe('createTemplateAction', () => { handler: async ctx => { // @ts-expect-error - repoUrl is string const a: number = ctx.input.repoUrl; - const b: string = ctx.input.repoUrl; - expect(b).toBeDefined(); - // @ts-expect-error - logStream is not available - const stream = ctx.logStream; - - expect(stream).toBeDefined(); + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + [a, b]; ctx.output('test', 'value'); @@ -128,4 +47,56 @@ describe('createTemplateAction', () => { expect(action).toBeDefined(); }); + + it('should allow creating with a function for input and output schema for more complex types', () => { + const action = createTemplateAction({ + id: 'test', + schema: { + input: z => + z.union([ + z.object({ + repoUrl: z.string(), + }), + z.object({ + numberThing: z.number(), + }), + ]), + output: z => + z.object({ + test: z.string(), + }), + }, + handler: async ctx => { + ctx.output('test', 'value'); + + // @ts-expect-error - not valid output type + ctx.output('test', 4); + + // @ts-expect-error - not valid output name + ctx.output('test2', 'value'); + + if ('repoUrl' in ctx.input) { + // @ts-expect-error - not valid input type + const a: number = ctx.input.repoUrl; + + const b: string = ctx.input.repoUrl; + + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + [a, b]; + } + + if ('numberThing' in ctx.input) { + const a: number = ctx.input.numberThing; + + // @ts-expect-error - not valid input type + const b: string = ctx.input.numberThing; + + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + [a, b]; + } + }, + }); + + expect(action).toBeDefined(); + }); }); diff --git a/plugins/scaffolder-node/src/actions/createTemplateAction.ts b/plugins/scaffolder-node/src/actions/createTemplateAction.ts index ce38f3b4e1..edf3e203b9 100644 --- a/plugins/scaffolder-node/src/actions/createTemplateAction.ts +++ b/plugins/scaffolder-node/src/actions/createTemplateAction.ts @@ -30,14 +30,18 @@ export type TemplateActionOptions< TActionInput extends JsonObject = {}, TActionOutput extends JsonObject = {}, TInputSchema extends - | JsonObject - | z.ZodType - | { [key in string]: (zImpl: typeof z) => z.ZodType } = JsonObject, + | { [key in string]: (zImpl: typeof z) => z.ZodType } + | ((zImpl: typeof z) => z.ZodType) = { + [key in string]: (zImpl: typeof z) => z.ZodType; + }, TOutputSchema extends - | JsonObject - | z.ZodType - | { [key in string]: (zImpl: typeof z) => z.ZodType } = JsonObject, - TSchemaType extends 'v1' | 'v2' = 'v1' | 'v2', + | { + [key in string]: (zImpl: typeof z) => z.ZodType; + } + | ((zImpl: typeof z) => z.ZodType) = { + [key in string]: (zImpl: typeof z) => z.ZodType; + }, + TSchemaType extends 'v2' = 'v2', > = { id: string; description?: string; @@ -62,109 +66,82 @@ type FlattenOptionalProperties = Expand< [K in keyof T as undefined extends T[K] ? K : never]?: T[K]; } >; - -/** - * @public - * @deprecated migrate to using the new built in zod schema definitions for schemas - */ -export function createTemplateAction< - TInputParams extends JsonObject = JsonObject, - TOutputParams extends JsonObject = JsonObject, - TInputSchema extends JsonObject = JsonObject, - TOutputSchema extends JsonObject = JsonObject, - TActionInput extends JsonObject = TInputParams, - TActionOutput extends JsonObject = TOutputParams, ->( - action: TemplateActionOptions< - TActionInput, - TActionOutput, - TInputSchema, - TOutputSchema, - 'v1' - >, -): TemplateAction; -/** - * @public - * @deprecated migrate to using the new built in zod schema definitions for schemas - */ -export function createTemplateAction< - TInputParams extends JsonObject = JsonObject, - TOutputParams extends JsonObject = JsonObject, - TInputSchema extends z.ZodType = z.ZodType, - TOutputSchema extends z.ZodType = z.ZodType, - TActionInput extends JsonObject = z.infer, - TActionOutput extends JsonObject = z.infer, ->( - action: TemplateActionOptions< - TActionInput, - TActionOutput, - TInputSchema, - TOutputSchema, - 'v1' - >, -): TemplateAction; /** * This function is used to create new template actions to get type safety. * Will convert zod schemas to json schemas for use throughout the system. * @public */ export function createTemplateAction< - TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }, - TOutputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }, + TInputSchema extends + | { [key in string]: (zImpl: typeof z) => z.ZodType } + | ((zImpl: typeof z) => z.ZodType), + TOutputSchema extends + | { [key in string]: (zImpl: typeof z) => z.ZodType } + | ((zImpl: typeof z) => z.ZodType), >( action: TemplateActionOptions< - { - [key in keyof TInputSchema]: z.infer>; - }, - { - [key in keyof TOutputSchema]: z.infer>; - }, + TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } + ? { + [key in keyof TInputSchema]: z.infer>; + } + : TInputSchema extends (zImpl: typeof z) => z.ZodType + ? z.infer> + : never, + TOutputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } + ? { + [key in keyof TOutputSchema]: z.infer>; + } + : TOutputSchema extends (zImpl: typeof z) => z.ZodType + ? z.infer> + : never, TInputSchema, TOutputSchema, 'v2' >, ): TemplateAction< - FlattenOptionalProperties<{ - [key in keyof TInputSchema]: z.output>; - }>, - FlattenOptionalProperties<{ - [key in keyof TOutputSchema]: z.output>; - }>, + FlattenOptionalProperties< + TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } + ? { + [key in keyof TInputSchema]: z.output>; + } + : TInputSchema extends (zImpl: typeof z) => z.ZodType + ? z.output> + : never + >, + FlattenOptionalProperties< + TOutputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } + ? { + [key in keyof TOutputSchema]: z.output< + ReturnType + >; + } + : TOutputSchema extends (zImpl: typeof z) => z.ZodType + ? z.output> + : never + >, 'v2' >; export function createTemplateAction< - TInputParams extends JsonObject = JsonObject, - TOutputParams extends JsonObject = JsonObject, - TInputSchema extends - | JsonObject - | z.ZodType - | { [key in string]: (zImpl: typeof z) => z.ZodType } = JsonObject, - TOutputSchema extends - | JsonObject - | z.ZodType - | { [key in string]: (zImpl: typeof z) => z.ZodType } = JsonObject, - TActionInput extends JsonObject = TInputSchema extends z.ZodType< - any, - any, - infer IReturn - > - ? IReturn - : TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } + TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } = { + [key in string]: (zImpl: typeof z) => z.ZodType; + }, + TOutputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } = { + [key in string]: (zImpl: typeof z) => z.ZodType; + }, + TActionInput extends JsonObject = TInputSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + } ? Expand<{ [key in keyof TInputSchema]: z.infer>; }> - : TInputParams, - TActionOutput extends JsonObject = TOutputSchema extends z.ZodType< - any, - any, - infer IReturn - > - ? IReturn - : TOutputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } + : never, + TActionOutput extends JsonObject = TOutputSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + } ? Expand<{ [key in keyof TOutputSchema]: z.infer>; }> - : TOutputParams, + : never, >( action: TemplateActionOptions< TActionInput, @@ -172,13 +149,7 @@ export function createTemplateAction< TInputSchema, TOutputSchema >, -): TemplateAction< - TActionInput, - TActionOutput, - TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType } - ? 'v2' - : 'v1' -> { +): TemplateAction { const { inputSchema, outputSchema } = parseSchemas( action as TemplateActionOptions, ); diff --git a/plugins/scaffolder-node/src/actions/gitHelpers.ts b/plugins/scaffolder-node/src/actions/gitHelpers.ts index 8922975274..49650de92e 100644 --- a/plugins/scaffolder-node/src/actions/gitHelpers.ts +++ b/plugins/scaffolder-node/src/actions/gitHelpers.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { Logger } from 'winston'; import { Git } from '../scm'; +import { LoggerService } from '@backstage/backend-plugin-api'; /** * @public @@ -27,7 +27,7 @@ export async function initRepoAndPush(input: { // it has to be provided as password together with a username // which may be a fixed value defined by the provider. auth: { username: string; password: string } | { token: string }; - logger: Logger; + logger: LoggerService; defaultBranch?: string; commitMessage?: string; gitAuthorInfo?: { name?: string; email?: string }; @@ -87,7 +87,7 @@ export async function commitAndPushRepo(input: { // it has to be provided as password together with a username // which may be a fixed value defined by the provider. auth: { username: string; password: string } | { token: string }; - logger: Logger; + logger: LoggerService; commitMessage: string; gitAuthorInfo?: { name?: string; email?: string }; branch?: string; @@ -147,7 +147,7 @@ export async function cloneRepo(options: { // it has to be provided as password together with a username // which may be a fixed value defined by the provider. auth: { username: string; password: string } | { token: string }; - logger?: Logger | undefined; + logger?: LoggerService | undefined; ref?: string | undefined; depth?: number | undefined; noCheckout?: boolean | undefined; @@ -172,7 +172,7 @@ export async function createBranch(options: { // it has to be provided as password together with a username // which may be a fixed value defined by the provider. auth: { username: string; password: string } | { token: string }; - logger?: Logger | undefined; + logger?: LoggerService | undefined; }): Promise { const { dir, ref, auth, logger } = options; const git = Git.fromAuth({ @@ -193,7 +193,7 @@ export async function addFiles(options: { // it has to be provided as password together with a username // which may be a fixed value defined by the provider. auth: { username: string; password: string } | { token: string }; - logger?: Logger | undefined; + logger?: LoggerService | undefined; }): Promise { const { dir, filepath, auth, logger } = options; const git = Git.fromAuth({ @@ -213,7 +213,7 @@ export async function commitAndPushBranch(options: { // it has to be provided as password together with a username // which may be a fixed value defined by the provider. auth: { username: string; password: string } | { token: string }; - logger?: Logger | undefined; + logger?: LoggerService | undefined; commitMessage: string; gitAuthorInfo?: { name?: string; email?: string }; branch?: string; diff --git a/plugins/scaffolder-node/src/actions/index.ts b/plugins/scaffolder-node/src/actions/index.ts index c1c8551c53..686d89d844 100644 --- a/plugins/scaffolder-node/src/actions/index.ts +++ b/plugins/scaffolder-node/src/actions/index.ts @@ -33,4 +33,8 @@ export { createBranch, cloneRepo, } from './gitHelpers'; -export { parseRepoUrl, getRepoSourceDirectory } from './util'; +export { + parseRepoUrl, + getRepoSourceDirectory, + isNotGitDirectoryOrContents, +} from './util'; diff --git a/plugins/scaffolder-node/src/actions/types.ts b/plugins/scaffolder-node/src/actions/types.ts index 92a2c7464a..f13d256332 100644 --- a/plugins/scaffolder-node/src/actions/types.ts +++ b/plugins/scaffolder-node/src/actions/types.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -import { Logger } from 'winston'; -import { Writable } from 'stream'; import { JsonObject, JsonValue } from '@backstage/types'; import { TaskSecrets } from '../tasks'; import { TemplateInfo } from '@backstage/plugin-scaffolder-common'; @@ -32,143 +30,75 @@ import { export type ActionContext< TActionInput extends JsonObject, TActionOutput extends JsonObject = JsonObject, - TSchemaType extends 'v1' | 'v2' = 'v1', -> = TSchemaType extends 'v2' - ? { - logger: LoggerService; - secrets?: TaskSecrets; - workspacePath: string; - input: TActionInput; - checkpoint(opts: { - key: string; - fn: () => Promise | T; - }): Promise; - output( - name: keyof TActionOutput, - value: TActionOutput[keyof TActionOutput], - ): void; - /** - * Creates a temporary directory for use by the action, which is then cleaned up automatically. - */ - createTemporaryDirectory(): Promise; + _TSchemaType extends 'v2' = 'v2', +> = { + logger: LoggerService; + secrets?: TaskSecrets; + workspacePath: string; + input: TActionInput; + checkpoint(opts: { + key: string; + fn: () => Promise | T; + }): Promise; + output( + name: keyof TActionOutput, + value: TActionOutput[keyof TActionOutput], + ): void; + /** + * Creates a temporary directory for use by the action, which is then cleaned up automatically. + */ + createTemporaryDirectory(): Promise; - /** - * Get the credentials for the current request - */ - getInitiatorCredentials(): Promise; + /** + * Get the credentials for the current request + */ + getInitiatorCredentials(): Promise; - /** - * Task information - */ - task: { - id: string; - }; + /** + * Task information + */ + task: { + id: string; + }; - templateInfo?: TemplateInfo; + templateInfo?: TemplateInfo; - /** - * Whether this action invocation is a dry-run or not. - * This will only ever be true if the actions as marked as supporting dry-runs. - */ - isDryRun?: boolean; + /** + * Whether this action invocation is a dry-run or not. + * This will only ever be true if the actions as marked as supporting dry-runs. + */ + isDryRun?: boolean; - /** - * The user which triggered the action. - */ - user?: { - /** - * The decorated entity from the Catalog - */ - entity?: UserEntity; - /** - * An entity ref for the author of the task - */ - ref?: string; - }; + /** + * The user which triggered the action. + */ + user?: { + /** + * The decorated entity from the Catalog + */ + entity?: UserEntity; + /** + * An entity ref for the author of the task + */ + ref?: string; + }; - /** - * Implement the signal to make your custom step abortable https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal - */ - signal?: AbortSignal; + /** + * Implement the signal to make your custom step abortable https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal + */ + signal?: AbortSignal; - /** - * Optional value of each invocation - */ - each?: JsonObject; - } - : /** @deprecated **/ - { - // TODO(blam): move this to LoggerService - logger: Logger; - /** @deprecated - use `ctx.logger` instead */ - logStream: Writable; - secrets?: TaskSecrets; - workspacePath: string; - input: TActionInput; - checkpoint(opts: { - key: string; - fn: () => Promise | T; - }): Promise; - output( - name: keyof TActionOutput, - value: TActionOutput[keyof TActionOutput], - ): void; - - /** - * Creates a temporary directory for use by the action, which is then cleaned up automatically. - */ - createTemporaryDirectory(): Promise; - - /** - * Get the credentials for the current request - */ - getInitiatorCredentials(): Promise; - - /** - * Task information - */ - task: { - id: string; - }; - - templateInfo?: TemplateInfo; - - /** - * Whether this action invocation is a dry-run or not. - * This will only ever be true if the actions as marked as supporting dry-runs. - */ - isDryRun?: boolean; - - /** - * The user which triggered the action. - */ - user?: { - /** - * The decorated entity from the Catalog - */ - entity?: UserEntity; - /** - * An entity ref for the author of the task - */ - ref?: string; - }; - - /** - * Implement the signal to make your custom step abortable https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal - */ - signal?: AbortSignal; - - /** - * Optional value of each invocation - */ - each?: JsonObject; - }; + /** + * Optional value of each invocation + */ + each?: JsonObject; +}; /** @public */ export type TemplateAction< TActionInput extends JsonObject = JsonObject, TActionOutput extends JsonObject = JsonObject, - TSchemaType extends 'v1' | 'v2' = 'v1', + TSchemaType extends 'v2' = 'v2', > = { id: string; description?: string; diff --git a/plugins/scaffolder-node/src/actions/util.test.ts b/plugins/scaffolder-node/src/actions/util.test.ts index 4b1e787bbc..792e306d00 100644 --- a/plugins/scaffolder-node/src/actions/util.test.ts +++ b/plugins/scaffolder-node/src/actions/util.test.ts @@ -244,4 +244,30 @@ describe('scaffolder action utils', () => { }); }); }); + + describe('isNotGitDirectoryOrContents', () => { + it('should filter .git directory and its contents but keep other files', () => { + // Import the function to test + const { isNotGitDirectoryOrContents } = require('./util'); + + // Should filter out .git directory + expect(isNotGitDirectoryOrContents('.git')).toBe(false); + + // Should filter out .git directory in subdirectories + expect(isNotGitDirectoryOrContents('subdir/.git')).toBe(false); + + // Should filter out files inside .git directory + expect(isNotGitDirectoryOrContents('.git/config')).toBe(false); + expect(isNotGitDirectoryOrContents('subdir/.git/config')).toBe(false); + + // Should keep .gitignore and other non-.git-directory files + expect(isNotGitDirectoryOrContents('.gitignore')).toBe(true); + expect(isNotGitDirectoryOrContents('src/components/GitHubIcon.js')).toBe( + true, + ); + expect(isNotGitDirectoryOrContents('.github/workflows/ci.yml')).toBe( + true, + ); + }); + }); }); diff --git a/plugins/scaffolder-node/src/actions/util.ts b/plugins/scaffolder-node/src/actions/util.ts index 12f73b0c6c..34cc74c811 100644 --- a/plugins/scaffolder-node/src/actions/util.ts +++ b/plugins/scaffolder-node/src/actions/util.ts @@ -130,11 +130,7 @@ function checkRequiredParams(repoUrl: URL, ...params: string[]) { } } -const isZodSchema = (schema: unknown): schema is z.ZodType => { - return typeof schema === 'object' && !!schema && 'safeParseAsync' in schema; -}; - -const isNativeZodSchema = ( +const isKeyValueZodCallback = ( schema: unknown, ): schema is { [key in string]: (zImpl: typeof z) => z.ZodType } => { return ( @@ -144,23 +140,20 @@ const isNativeZodSchema = ( ); }; +const isZodFunctionDefinition = ( + schema: unknown, +): schema is (zImpl: typeof z) => z.ZodType => { + return typeof schema === 'function'; +}; + export const parseSchemas = ( - action: TemplateActionOptions, + action: TemplateActionOptions, ): { inputSchema?: Schema; outputSchema?: Schema } => { if (!action.schema) { return { inputSchema: undefined, outputSchema: undefined }; } - if (isZodSchema(action.schema.input)) { - return { - inputSchema: zodToJsonSchema(action.schema.input) as Schema, - outputSchema: isZodSchema(action.schema.output) - ? (zodToJsonSchema(action.schema.output) as Schema) - : undefined, - }; - } - - if (isNativeZodSchema(action.schema.input)) { + if (isKeyValueZodCallback(action.schema.input)) { const input = z.object( Object.fromEntries( Object.entries(action.schema.input).map(([k, v]) => [k, v(z)]), @@ -169,7 +162,7 @@ export const parseSchemas = ( return { inputSchema: zodToJsonSchema(input) as Schema, - outputSchema: isNativeZodSchema(action.schema.output) + outputSchema: isKeyValueZodCallback(action.schema.output) ? (zodToJsonSchema( z.object( Object.fromEntries( @@ -181,8 +174,26 @@ export const parseSchemas = ( }; } + if (isZodFunctionDefinition(action.schema.input)) { + return { + inputSchema: zodToJsonSchema(action.schema.input(z)) as Schema, + outputSchema: isZodFunctionDefinition(action.schema.output) + ? (zodToJsonSchema(action.schema.output(z)) as Schema) + : undefined, + }; + } + return { - inputSchema: action.schema.input, - outputSchema: action.schema.output, + inputSchema: undefined, + outputSchema: undefined, }; }; + +/** + * Filter function to exclude the .git directory and its contents + * while keeping other files like .gitignore + * @public + */ +export function isNotGitDirectoryOrContents(path: string): boolean { + return !(path.endsWith('.git') || path.includes('.git/')); +} diff --git a/plugins/scaffolder-react/CHANGELOG.md b/plugins/scaffolder-react/CHANGELOG.md index 9222d683b6..b9561731f3 100644 --- a/plugins/scaffolder-react/CHANGELOG.md +++ b/plugins/scaffolder-react/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-scaffolder-react +## 1.16.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/theme@0.6.6 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/plugin-scaffolder-common@1.5.11 + +## 1.16.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + ## 1.16.0 ### Minor Changes diff --git a/plugins/scaffolder-react/package.json b/plugins/scaffolder-react/package.json index 2259150720..d0e2c6344b 100644 --- a/plugins/scaffolder-react/package.json +++ b/plugins/scaffolder-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-react", - "version": "1.16.0", + "version": "1.16.1-next.1", "description": "A frontend library that helps other Backstage plugins interact with the Scaffolder", "backstage": { "role": "web-library", diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index be25d25bc5..82b8029637 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,39 @@ # @backstage/plugin-scaffolder +## 1.31.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/plugin-scaffolder-react@1.16.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/integration@1.17.0 + - @backstage/integration-react@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-react@0.4.34 + - @backstage/plugin-scaffolder-common@1.5.11 + +## 1.31.1-next.0 + +### Patch Changes + +- d781b33: render details for composite property schemas +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + - @backstage/plugin-scaffolder-react@1.16.1-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + - @backstage/integration-react@1.2.7 + ## 1.31.0 ### Minor Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 9d7e3cc610..afcb93ff68 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "1.31.0", + "version": "1.31.1-next.1", "description": "The Backstage plugin that helps you create new things", "backstage": { "role": "frontend-plugin", diff --git a/plugins/scaffolder/report-alpha.api.md b/plugins/scaffolder/report-alpha.api.md index 69fcec11e6..54acadd561 100644 --- a/plugins/scaffolder/report-alpha.api.md +++ b/plugins/scaffolder/report-alpha.api.md @@ -8,6 +8,8 @@ import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ApiRef } from '@backstage/frontend-plugin-api'; import { ComponentType } from 'react'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { Entity } from '@backstage/catalog-model'; +import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; @@ -18,6 +20,7 @@ import type { FormProps as FormProps_2 } from '@rjsf/core'; import { FormProps as FormProps_3 } from '@backstage/plugin-scaffolder-react'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; +import { IconLinkVerticalProps } from '@backstage/core-components'; import { JSX as JSX_2 } from 'react'; import { LayoutOptions } from '@backstage/plugin-scaffolder-react'; import { PathParams } from '@backstage/core-plugin-api'; @@ -121,6 +124,45 @@ const _default: FrontendPlugin< factory: AnyApiFactory; }; }>; + 'entity-icon-link:scaffolder/launch-template': ExtensionDefinition<{ + kind: 'entity-icon-link'; + name: 'launch-template'; + config: { + label: string | undefined; + title: string | undefined; + filter: EntityPredicate | undefined; + }; + configInput: { + filter?: EntityPredicate | undefined; + label?: string | undefined; + title?: string | undefined; + }; + output: + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + () => IconLinkVerticalProps, + 'entity-icon-link-props', + {} + >; + inputs: {}; + params: { + useProps: () => Omit; + filter?: EntityPredicate | ((entity: Entity) => boolean); + }; + }>; 'nav-item:scaffolder': ExtensionDefinition<{ kind: 'nav-item'; name: undefined; @@ -325,6 +367,7 @@ export const scaffolderTranslationRef: TranslationRef< readonly 'fields.repoUrlPicker.repository.title': 'Repositories Available'; readonly 'fields.repoUrlPicker.repository.description': 'The name of the repository'; readonly 'fields.repoUrlPicker.repository.inputTitle': 'Repository'; + readonly 'aboutCard.launchTemplate': 'Launch Template'; readonly 'actionsPage.content.emptyState.title': 'No information to display'; readonly 'actionsPage.content.emptyState.description': 'There are no actions installed or there was an issue communicating with backend.'; readonly 'actionsPage.content.searchFieldPlaceholder': 'Search for an action'; diff --git a/plugins/scaffolder/src/alpha/hooks/useScaffolderTemplateIconLinkProps.tsx b/plugins/scaffolder/src/alpha/hooks/useScaffolderTemplateIconLinkProps.tsx new file mode 100644 index 0000000000..87754468fa --- /dev/null +++ b/plugins/scaffolder/src/alpha/hooks/useScaffolderTemplateIconLinkProps.tsx @@ -0,0 +1,53 @@ +/* + * 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 { DEFAULT_NAMESPACE } from '@backstage/catalog-model'; +import { useApp, useRouteRef } from '@backstage/core-plugin-api'; +import { useEntity } from '@backstage/plugin-catalog-react'; + +import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; +import { taskCreatePermission } from '@backstage/plugin-scaffolder-common/alpha'; +import { usePermission } from '@backstage/plugin-permission-react'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; + +import { scaffolderTranslationRef } from '../../translation'; +import { selectedTemplateRouteRef } from '../../routes'; + +// Note: If you update this hook, please also update the "useScaffolderTemplateIconLinkProps" hook +// in the "plugins/catalog/src/components/AboutCard/AboutCard.tsx" file +/** @alpha */ +export function useScaffolderTemplateIconLinkProps() { + const app = useApp(); + const { entity } = useEntity(); + const templateRoute = useRouteRef(selectedTemplateRouteRef); + const { t } = useTranslationRef(scaffolderTranslationRef); + const Icon = app.getSystemIcon('scaffolder') ?? CreateComponentIcon; + const { allowed: canCreateTemplateTask } = usePermission({ + permission: taskCreatePermission, + }); + + return { + label: t('aboutCard.launchTemplate'), + icon: , + disabled: !templateRoute || !canCreateTemplateTask, + href: + templateRoute && + templateRoute({ + templateName: entity.metadata.name, + namespace: entity.metadata.namespace || DEFAULT_NAMESPACE, + }), + }; +} diff --git a/plugins/scaffolder/src/alpha/plugin.tsx b/plugins/scaffolder/src/alpha/plugin.tsx index aa37f80493..a414c61da5 100644 --- a/plugins/scaffolder/src/alpha/plugin.tsx +++ b/plugins/scaffolder/src/alpha/plugin.tsx @@ -33,12 +33,25 @@ import { scaffolderPage, scaffolderApi, } from './extensions'; +import { isTemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { formFieldsApi } from '@backstage/plugin-scaffolder-react/alpha'; import { formDecoratorsApi } from './api'; +import { EntityIconLinkBlueprint } from '@backstage/plugin-catalog-react/alpha'; +import { useScaffolderTemplateIconLinkProps } from './hooks/useScaffolderTemplateIconLinkProps'; + +/** @alpha */ +const scaffolderEntityIconLink = EntityIconLinkBlueprint.make({ + name: 'launch-template', + params: { + filter: isTemplateEntityV1beta3, + useProps: useScaffolderTemplateIconLinkProps, + }, +}); /** @alpha */ export default createFrontendPlugin({ pluginId: 'scaffolder', + info: { packageJson: () => import('../../package.json') }, routes: convertLegacyRouteRefs({ root: rootRouteRef, selectedTemplate: selectedTemplateRouteRef, @@ -56,6 +69,7 @@ export default createFrontendPlugin({ scaffolderApi, scaffolderPage, scaffolderNavItem, + scaffolderEntityIconLink, formDecoratorsApi, formFieldsApi, repoUrlPickerFormField, diff --git a/plugins/scaffolder/src/components/RenderSchema/RenderSchema.test.tsx b/plugins/scaffolder/src/components/RenderSchema/RenderSchema.test.tsx index 0f1150591d..ec49f414f9 100644 --- a/plugins/scaffolder/src/components/RenderSchema/RenderSchema.test.tsx +++ b/plugins/scaffolder/src/components/RenderSchema/RenderSchema.test.tsx @@ -475,7 +475,7 @@ describe('JSON schema UI rendering', () => { const tr = getByTestId(`properties-row_test.${k}`); expect(tr).toBeInTheDocument(); - const sub = getByTestId(`properties-row_test_sub${i}.${k}`); + const sub = getByTestId(`properties-row_test_oneOf${i}.${k}`); expect(sub).toBeInTheDocument(); expect( @@ -485,7 +485,7 @@ describe('JSON schema UI rendering', () => { ).toBe(true); expect( - getByTestId(`properties-row_test_sub${i}.${k}Flag`), + getByTestId(`properties-row_test_oneOf${i}.${k}Flag`), ).toBeInTheDocument(); } }); @@ -528,10 +528,35 @@ describe('JSON schema UI rendering', () => { for (const i of subs.keys()) { for (const k of Object.keys(subs[i].properties!)) { expect( - rendered.getByTestId(`properties-row_test_sub${i}.${k}`), + rendered.getByTestId(`properties-row_test_oneOf${i}.${k}`), ).toBeInTheDocument(); } } }); + it('property alternatives', async () => { + const schema: JSONSchema7 = { + properties: { + bs: { + anyOf: [ + { + type: 'boolean', + }, + { + type: 'string', + }, + ], + }, + }, + }; + const rendered = await renderInTestApp( + , + ); + expect( + rendered.getByTestId(`root-row_test.bs_anyOf0`), + ).toBeInTheDocument(); + expect( + rendered.getByTestId(`root-row_test.bs_anyOf1`), + ).toBeInTheDocument(); + }); }); }); diff --git a/plugins/scaffolder/src/components/RenderSchema/RenderSchema.tsx b/plugins/scaffolder/src/components/RenderSchema/RenderSchema.tsx index 8f967f8ee2..fc926e6c44 100644 --- a/plugins/scaffolder/src/components/RenderSchema/RenderSchema.tsx +++ b/plugins/scaffolder/src/components/RenderSchema/RenderSchema.tsx @@ -41,37 +41,44 @@ import { JSONSchema7Definition, JSONSchema7Type, } from 'json-schema'; -import { FC, JSX, cloneElement, Fragment } from 'react'; +import { FC, JSX, cloneElement, Fragment, ReactElement } from 'react'; import { scaffolderTranslationRef } from '../../translation'; import { SchemaRenderContext, SchemaRenderStrategy } from './types'; import { TranslationMessages } from '../TemplatingExtensionsPage/types'; -const getTypes = (properties: JSONSchema7) => { - if (!properties.type) { +const compositeSchemaProperties = ['allOf', 'anyOf', 'not', 'oneOf'] as const; + +type subSchemasType = { + [K in (typeof compositeSchemaProperties)[number]]?: JSONSchema7Definition[]; +}; + +const getTypes = (schema: JSONSchema7) => { + if (!schema.type) { + if ( + Object.getOwnPropertyNames(schema).some(p => + compositeSchemaProperties.includes(p as any), + ) + ) { + return undefined; + } return ['unknown']; } - if (properties.type !== 'array') { - return [properties.type].flat(); + if (schema.type !== 'array') { + return [schema.type].flat(); } return [ - `${properties.type}(${ - (properties.items as JSONSchema7 | undefined)?.type ?? 'unknown' + `${schema.type}(${ + (schema.items as JSONSchema7 | undefined)?.type ?? 'unknown' })`, ]; }; -const getSubschemas = ( - schema: JSONSchema7Definition, -): Record => { +const getSubschemas = (schema: JSONSchema7Definition): subSchemasType => { if (typeof schema === 'boolean') { return {}; } const base: Omit = {}; - const compositeSchemaProperties = ['allOf', 'anyOf', 'not', 'oneOf'] as const; - type subSchemasType = { - [K in (typeof compositeSchemaProperties)[number]]?: JSONSchema7Definition[]; - }; const subschemas: subSchemasType = {}; for (const [key, value] of Object.entries(schema) as [ @@ -224,7 +231,10 @@ const inspectSchema = ( return { canSubschema: false, hasEnum: false }; } return { - canSubschema: getTypes(schema).some(t => t.includes('object')), + canSubschema: + Object.getOwnPropertyNames(schema).some(p => + compositeSchemaProperties.includes(p as any), + ) || getTypes(schema)!.some(t => t.includes('object')), hasEnum: !!enumFrom(schema), }; }; @@ -242,8 +252,8 @@ const typeColumn = { const info = inspectSchema(element.schema); return ( <> - {types.map((type, index) => - type.includes('object') || (info.hasEnum && index === 0) ? ( + {types?.map((type, index) => + info.canSubschema || (info.hasEnum && index === 0) ? ( { if (typeof schema === 'object') { - const subschemas = - strategy === 'root' || !context.parent ? getSubschemas(schema) : {}; + const subschemas = getSubschemas(schema); let columns: Column[] | undefined; let elements: SchemaRenderElement[] | undefined; if (strategy === 'root') { @@ -393,90 +402,100 @@ export const RenderSchema = ({ {elements.map(el => { const id = generateId(el, context); const info = inspectSchema(el.schema); - return ( - - - {columns!.map(col => ( - + {columns!.map(col => ( + + {col.render(el, context)} + + ))} + , + ]; + if ( + typeof el.schema !== 'boolean' && + (info.canSubschema || info.hasEnum) + ) { + let details: ReactElement = ( + + {info.canSubschema && ( + - {col.render(el, context)} - - ))} - - {typeof el.schema !== 'boolean' && - (info.canSubschema || info.hasEnum) && ( - - - - - {info.canSubschema && ( - - )} - {info.hasEnum && ( - <> - {cloneElement( - context.headings[0], - {}, - 'Valid values:', - )} - - - )} - - - - + /> )} - - ); + {info.hasEnum && ( + <> + {cloneElement( + context.headings[0], + {}, + 'Valid values:', + )} + + + )} + + ); + if (getTypes(el.schema)) { + details = ( + + {details} + + ); + } + rows.push( + + + {details} + + , + ); + } + return {rows}; })} )} - {Object.keys(subschemas).map(sk => ( + {(Object.keys(subschemas) as Array).map(sk => ( {cloneElement(context.headings[0], {}, sk)} - {subschemas[sk].map((sub, index) => ( + {subschemas[sk]!.map((sub, index) => ( { const clearButtonEndAdornment = ( ); diff --git a/plugins/search-react/src/translation.ts b/plugins/search-react/src/translation.ts index 6897911949..e4c394d952 100644 --- a/plugins/search-react/src/translation.ts +++ b/plugins/search-react/src/translation.ts @@ -25,6 +25,7 @@ export const searchReactTranslationRef = createTranslationRef({ searchBar: { title: 'Search', placeholder: 'Search in {{org}}', + clearButtonTitle: 'Clear', }, searchFilter: { allOptionTitle: 'All', diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index 0bc6cd8147..9b9f4a532d 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-search +## 1.4.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-search-react@1.9.1-next.0 + +## 1.4.27-next.0 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + - @backstage/plugin-search-react@1.9.1-next.0 + ## 1.4.26 ### Patch Changes diff --git a/plugins/search/package.json b/plugins/search/package.json index 2e621e9bff..ca31eb1244 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search", - "version": "1.4.26", + "version": "1.4.27-next.1", "description": "The Backstage plugin that provides your backstage app with search", "backstage": { "role": "frontend-plugin", diff --git a/plugins/search/src/alpha.tsx b/plugins/search/src/alpha.tsx index 5f55b46028..136f95f39e 100644 --- a/plugins/search/src/alpha.tsx +++ b/plugins/search/src/alpha.tsx @@ -279,6 +279,7 @@ export const searchNavItem = NavItemBlueprint.make({ /** @alpha */ export default createFrontendPlugin({ pluginId: 'search', + info: { packageJson: () => import('../package.json') }, extensions: [searchApi, searchPage, searchNavItem], routes: convertLegacyRouteRefs({ root: rootRouteRef, diff --git a/plugins/signals-backend/CHANGELOG.md b/plugins/signals-backend/CHANGELOG.md index 6db23620e4..3f09417863 100644 --- a/plugins/signals-backend/CHANGELOG.md +++ b/plugins/signals-backend/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-signals-backend +## 0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12-next.1 + - @backstage/plugin-signals-node@0.1.21-next.1 + +## 0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + - @backstage/plugin-signals-node@0.1.21-next.0 + ## 0.3.4 ### Patch Changes diff --git a/plugins/signals-backend/package.json b/plugins/signals-backend/package.json index a8d0041dec..cb9146c9c4 100644 --- a/plugins/signals-backend/package.json +++ b/plugins/signals-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals-backend", - "version": "0.3.4", + "version": "0.3.5-next.1", "backstage": { "role": "backend-plugin", "pluginId": "signals", diff --git a/plugins/signals-node/CHANGELOG.md b/plugins/signals-node/CHANGELOG.md index cc795e58b7..8e753081d8 100644 --- a/plugins/signals-node/CHANGELOG.md +++ b/plugins/signals-node/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-signals-node +## 0.1.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.12-next.1 + +## 0.1.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-events-node@0.4.12-next.0 + ## 0.1.20 ### Patch Changes diff --git a/plugins/signals-node/package.json b/plugins/signals-node/package.json index 29abd0da6a..4cb5c08f79 100644 --- a/plugins/signals-node/package.json +++ b/plugins/signals-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals-node", - "version": "0.1.20", + "version": "0.1.21-next.1", "description": "Node.js library for the signals plugin", "backstage": { "role": "node-library", diff --git a/plugins/signals/CHANGELOG.md b/plugins/signals/CHANGELOG.md index e095f3fe26..db16d0ade5 100644 --- a/plugins/signals/CHANGELOG.md +++ b/plugins/signals/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-signals +## 0.0.20-next.0 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + ## 0.0.19 ### Patch Changes diff --git a/plugins/signals/package.json b/plugins/signals/package.json index 5c4b84e0e8..5a5c17a82f 100644 --- a/plugins/signals/package.json +++ b/plugins/signals/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals", - "version": "0.0.19", + "version": "0.0.20-next.0", "backstage": { "role": "frontend-plugin", "pluginId": "signals", diff --git a/plugins/signals/src/alpha.tsx b/plugins/signals/src/alpha.tsx index ba7f72f420..90b78b05fd 100644 --- a/plugins/signals/src/alpha.tsx +++ b/plugins/signals/src/alpha.tsx @@ -45,5 +45,6 @@ const api = ApiBlueprint.make({ /** @alpha */ export default createFrontendPlugin({ pluginId: 'signals', + info: { packageJson: () => import('../package.json') }, extensions: [api], }); diff --git a/plugins/techdocs-addons-test-utils/CHANGELOG.md b/plugins/techdocs-addons-test-utils/CHANGELOG.md index 6ee9f12f2b..be8b0d5920 100644 --- a/plugins/techdocs-addons-test-utils/CHANGELOG.md +++ b/plugins/techdocs-addons-test-utils/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-techdocs-addons-test-utils +## 1.0.49-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.13.0-next.1 + - @backstage/plugin-catalog@1.31.0-next.1 + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/core-app-api@1.17.0 + - @backstage/core-plugin-api@1.10.7 + - @backstage/integration-react@1.2.7 + - @backstage/test-utils@1.7.8 + - @backstage/plugin-search-react@1.9.1-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + +## 1.0.49-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.13.0-next.0 + - @backstage/plugin-catalog@1.31.0-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + - @backstage/plugin-search-react@1.9.1-next.0 + - @backstage/integration-react@1.2.7 + ## 1.0.48 ### Patch Changes diff --git a/plugins/techdocs-addons-test-utils/package.json b/plugins/techdocs-addons-test-utils/package.json index fcb1932ec8..668f2bea88 100644 --- a/plugins/techdocs-addons-test-utils/package.json +++ b/plugins/techdocs-addons-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-addons-test-utils", - "version": "1.0.48", + "version": "1.0.49-next.1", "backstage": { "role": "web-library", "pluginId": "techdocs-addons", diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index 2e93d792b3..a24b2477ca 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-techdocs-backend +## 2.0.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.4.3-next.1 + - @backstage/plugin-catalog-node@1.17.1-next.1 + - @backstage/backend-defaults@0.10.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/plugin-catalog-common@1.1.4 + - @backstage/plugin-permission-common@0.9.0 + - @backstage/plugin-techdocs-common@0.1.1-next.0 + - @backstage/plugin-techdocs-node@1.13.4-next.1 + +## 2.0.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-common@0.1.1-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/backend-defaults@0.10.1-next.0 + - @backstage/plugin-techdocs-node@1.13.4-next.0 + - @backstage/plugin-catalog-node@1.17.1-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.4.3-next.0 + ## 2.0.2 ### Patch Changes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index ac82e9d1be..afb3694630 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "2.0.2", + "version": "2.0.3-next.1", "description": "The Backstage backend plugin that renders technical documentation for your components", "backstage": { "role": "backend-plugin", diff --git a/plugins/techdocs-common/CHANGELOG.md b/plugins/techdocs-common/CHANGELOG.md index 619884e4ec..bf2a3300ea 100644 --- a/plugins/techdocs-common/CHANGELOG.md +++ b/plugins/techdocs-common/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/plugin-techdocs-common +## 0.1.1-next.0 + +### Patch Changes + +- ec7b35d: Introduced `backstage.io/techdocs-entity-path` annotation which allows deep linking into another entities TechDocs in conjunction with `backstage.io/techdocs-entity`. + ## 0.1.0 ### Minor Changes diff --git a/plugins/techdocs-common/package.json b/plugins/techdocs-common/package.json index 001d1106a3..ef0a6c613e 100644 --- a/plugins/techdocs-common/package.json +++ b/plugins/techdocs-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-common", - "version": "0.1.0", + "version": "0.1.1-next.0", "description": "Common functionality for TechDocs", "backstage": { "role": "common-library", diff --git a/plugins/techdocs-common/report.api.md b/plugins/techdocs-common/report.api.md index c7e1829a28..56a86778a8 100644 --- a/plugins/techdocs-common/report.api.md +++ b/plugins/techdocs-common/report.api.md @@ -8,4 +8,8 @@ export const TECHDOCS_ANNOTATION = 'backstage.io/techdocs-ref'; // @public (undocumented) export const TECHDOCS_EXTERNAL_ANNOTATION = 'backstage.io/techdocs-entity'; + +// @public (undocumented) +export const TECHDOCS_EXTERNAL_PATH_ANNOTATION = + 'backstage.io/techdocs-entity-path'; ``` diff --git a/plugins/techdocs-common/src/constants.ts b/plugins/techdocs-common/src/constants.ts index 40187f41ce..a31951204e 100644 --- a/plugins/techdocs-common/src/constants.ts +++ b/plugins/techdocs-common/src/constants.ts @@ -18,3 +18,6 @@ export const TECHDOCS_ANNOTATION = 'backstage.io/techdocs-ref'; /** @public */ export const TECHDOCS_EXTERNAL_ANNOTATION = 'backstage.io/techdocs-entity'; +/** @public */ +export const TECHDOCS_EXTERNAL_PATH_ANNOTATION = + 'backstage.io/techdocs-entity-path'; diff --git a/plugins/techdocs-module-addons-contrib/CHANGELOG.md b/plugins/techdocs-module-addons-contrib/CHANGELOG.md index 2eb8e01652..978d09e195 100644 --- a/plugins/techdocs-module-addons-contrib/CHANGELOG.md +++ b/plugins/techdocs-module-addons-contrib/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-techdocs-module-addons-contrib +## 1.1.25-next.1 + +### Patch Changes + +- 9dde3ba: Improved Keyboard accessibility in techdocs. +- Updated dependencies + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/integration@1.17.0 + - @backstage/integration-react@1.2.7 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + +## 1.1.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + - @backstage/integration-react@1.2.7 + ## 1.1.24 ### Patch Changes diff --git a/plugins/techdocs-module-addons-contrib/package.json b/plugins/techdocs-module-addons-contrib/package.json index adbbe4a335..b0bbd9c36f 100644 --- a/plugins/techdocs-module-addons-contrib/package.json +++ b/plugins/techdocs-module-addons-contrib/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-module-addons-contrib", - "version": "1.1.24", + "version": "1.1.25-next.1", "description": "Plugin module for contributed TechDocs Addons", "backstage": { "role": "frontend-plugin-module", diff --git a/plugins/techdocs-module-addons-contrib/src/ExpandableNavigation/ExpandableNavigation.tsx b/plugins/techdocs-module-addons-contrib/src/ExpandableNavigation/ExpandableNavigation.tsx index 5e5bd939fc..83e34f411a 100644 --- a/plugins/techdocs-module-addons-contrib/src/ExpandableNavigation/ExpandableNavigation.tsx +++ b/plugins/techdocs-module-addons-contrib/src/ExpandableNavigation/ExpandableNavigation.tsx @@ -92,16 +92,52 @@ export const ExpandableNavigationAddon = () => { }, [expanded], ); - + const handleKeyPass = ( + event: React.KeyboardEvent, + toggleAction: () => void, + ) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + toggleAction(); + } + }; useEffect(() => { // There is no nested navs if (!checkboxToggles?.length) return; setHasNavSubLevels(true); checkboxToggles.forEach(item => { - if (shouldToggle(item)) item.click(); + item.tabIndex = 0; + const toggleAction = () => { + if (shouldToggle(item)) { + item.click(); + } + }; + // Add keyboard event listener + const keydownHandler = (event: KeyboardEvent) => { + handleKeyPass( + event as unknown as React.KeyboardEvent, + toggleAction, + ); + }; + item.addEventListener('keydown', keydownHandler); + item.addEventListener('click', toggleAction); + + // Clean up event listener or unmount + return () => { + item.removeEventListener('keydown', keydownHandler); + item.removeEventListener('click', toggleAction); + }; }); - }, [expanded, shouldToggle, checkboxToggles]); + }, [checkboxToggles, shouldToggle]); + useEffect(() => { + if (!checkboxToggles?.length) return; + checkboxToggles.forEach(item => { + if (shouldToggle(item)) { + item.click(); + } + }); + }, [expanded, checkboxToggles, shouldToggle]); const handleState = () => { setExpanded(prevState => ({ @@ -115,6 +151,9 @@ export const ExpandableNavigationAddon = () => { handleKeyPass(event, handleState)} + tabIndex={0} // Ensuring keyboard focus + aria-expanded={expanded?.expandAllNestedNavs} // Accessibility aria-label={ expanded?.expandAllNestedNavs ? 'collapse-nav' : 'expand-nav' } diff --git a/plugins/techdocs-node/CHANGELOG.md b/plugins/techdocs-node/CHANGELOG.md index b320d4ac37..85a11b495c 100644 --- a/plugins/techdocs-node/CHANGELOG.md +++ b/plugins/techdocs-node/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-techdocs-node +## 1.13.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/integration@1.17.0 + - @backstage/integration-aws-node@0.1.16 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-techdocs-common@0.1.1-next.0 + +## 1.13.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-common@0.1.1-next.0 + - @backstage/backend-plugin-api@1.4.0-next.0 + ## 1.13.3 ### Patch Changes diff --git a/plugins/techdocs-node/package.json b/plugins/techdocs-node/package.json index eb3365ebfb..25aaa92014 100644 --- a/plugins/techdocs-node/package.json +++ b/plugins/techdocs-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-node", - "version": "1.13.3", + "version": "1.13.4-next.1", "description": "Common node.js functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli", "backstage": { "role": "node-library", diff --git a/plugins/techdocs-react/CHANGELOG.md b/plugins/techdocs-react/CHANGELOG.md index 4129109118..5357f4c90d 100644 --- a/plugins/techdocs-react/CHANGELOG.md +++ b/plugins/techdocs-react/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-techdocs-react +## 1.3.0-next.0 + +### Minor Changes + +- ec7b35d: Introduced `backstage.io/techdocs-entity-path` annotation which allows deep linking into another entities TechDocs in conjunction with `backstage.io/techdocs-entity`. + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/plugin-techdocs-common@0.1.1-next.0 + ## 1.2.17 ### Patch Changes diff --git a/plugins/techdocs-react/package.json b/plugins/techdocs-react/package.json index b1be611d68..618af87698 100644 --- a/plugins/techdocs-react/package.json +++ b/plugins/techdocs-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-react", - "version": "1.2.17", + "version": "1.3.0-next.0", "description": "Shared frontend utilities for TechDocs and Addons", "backstage": { "role": "web-library", @@ -63,6 +63,7 @@ "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", + "@backstage/plugin-techdocs-common": "workspace:^", "@backstage/version-bridge": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/styles": "^4.11.0", diff --git a/plugins/techdocs-react/report.api.md b/plugins/techdocs-react/report.api.md index 23a9c9faaf..62ac6375fa 100644 --- a/plugins/techdocs-react/report.api.md +++ b/plugins/techdocs-react/report.api.md @@ -15,8 +15,21 @@ import { JSX as JSX_2 } from 'react/jsx-runtime'; import { MemoExoticComponent } from 'react'; import { PropsWithChildren } from 'react'; import { ReactNode } from 'react'; +import { RouteFunc } from '@backstage/core-plugin-api'; import { SetStateAction } from 'react'; +// @public +export const buildTechDocsURL: ( + entity: Entity, + routeFunc: + | RouteFunc<{ + namespace: string; + kind: string; + name: string; + }> + | undefined, +) => string | undefined; + // @public export function createTechDocsAddonExtension( options: TechDocsAddonOptions, @@ -27,6 +40,9 @@ export function createTechDocsAddonExtension( options: TechDocsAddonOptions, ): Extension<(props: TComponentProps) => JSX.Element | null>; +// @public +export function getEntityRootTechDocsPath(entity: Entity): string; + // @public export const SHADOW_DOM_STYLE_LOAD_EVENT = 'TECH_DOCS_SHADOW_DOM_STYLE_LOAD'; diff --git a/plugins/techdocs-react/src/helpers.ts b/plugins/techdocs-react/src/helpers.ts index 3f10fff2ba..e6daf99b26 100644 --- a/plugins/techdocs-react/src/helpers.ts +++ b/plugins/techdocs-react/src/helpers.ts @@ -15,7 +15,17 @@ */ import { Config } from '@backstage/config'; -import { CompoundEntityRef } from '@backstage/catalog-model'; +import { + CompoundEntityRef, + Entity, + getCompoundEntityRef, + parseEntityRef, +} from '@backstage/catalog-model'; +import { + TECHDOCS_EXTERNAL_ANNOTATION, + TECHDOCS_EXTERNAL_PATH_ANNOTATION, +} from '@backstage/plugin-techdocs-common'; +import { RouteFunc } from '@backstage/core-plugin-api'; /** * Lower-case entity triplets by default, but allow override. @@ -35,3 +45,67 @@ export function toLowercaseEntityRefMaybe( return entityRef; } + +/** + * Get the entity path annotation from the given entity and ensure it starts with a slash. + * + * @public + */ +export function getEntityRootTechDocsPath(entity: Entity): string { + let path = entity.metadata.annotations?.[TECHDOCS_EXTERNAL_PATH_ANNOTATION]; + if (!path) { + return ''; + } + if (!path.startsWith('/')) { + path = `/${path}`; + } + return path; +} + +/** + * Build the TechDocs URL for the given entity. This helper should be used anywhere there + * is a link to an entities TechDocs. + * + * @public + */ +export const buildTechDocsURL = ( + entity: Entity, + routeFunc: + | RouteFunc<{ + namespace: string; + kind: string; + name: string; + }> + | undefined, +) => { + if (!routeFunc) { + return undefined; + } + + let { namespace, kind, name } = getCompoundEntityRef(entity); + + if (entity.metadata.annotations?.[TECHDOCS_EXTERNAL_ANNOTATION]) { + try { + const techdocsRef = parseEntityRef( + entity.metadata.annotations?.[TECHDOCS_EXTERNAL_ANNOTATION], + ); + namespace = techdocsRef.namespace; + kind = techdocsRef.kind; + name = techdocsRef.name; + } catch { + // not a fan of this but we don't care if the parseEntityRef fails + } + } + + const url = routeFunc({ + namespace, + kind, + name, + }); + + // Add on the external entity path to the url if one exists. This allows deep linking into another + // entities TechDocs. + const path = getEntityRootTechDocsPath(entity); + + return `${url}${path}`; +}; diff --git a/plugins/techdocs-react/src/index.ts b/plugins/techdocs-react/src/index.ts index 5cbab1a326..7c4f316c1e 100644 --- a/plugins/techdocs-react/src/index.ts +++ b/plugins/techdocs-react/src/index.ts @@ -52,4 +52,8 @@ export { useShadowRootElements, useShadowRootSelection, } from './hooks'; -export { toLowercaseEntityRefMaybe } from './helpers'; +export { + toLowercaseEntityRefMaybe, + getEntityRootTechDocsPath, + buildTechDocsURL, +} from './helpers'; diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index b453417cd4..9b70f65408 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,47 @@ # @backstage/plugin-techdocs +## 1.13.0-next.1 + +### Patch Changes + +- 9dde3ba: Improved Keyboard accessibility in techdocs. +- Updated dependencies + - @backstage/catalog-client@1.10.1-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/integration@1.17.0 + - @backstage/integration-react@1.2.7 + - @backstage/theme@0.6.6 + - @backstage/plugin-auth-react@0.1.15 + - @backstage/plugin-search-common@1.2.18 + - @backstage/plugin-search-react@1.9.1-next.0 + - @backstage/plugin-techdocs-common@0.1.1-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + +## 1.13.0-next.0 + +### Minor Changes + +- ec7b35d: Introduced `backstage.io/techdocs-entity-path` annotation which allows deep linking into another entities TechDocs in conjunction with `backstage.io/techdocs-entity`. + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/plugin-techdocs-react@1.3.0-next.0 + - @backstage/plugin-techdocs-common@0.1.1-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + - @backstage/plugin-search-react@1.9.1-next.0 + - @backstage/integration-react@1.2.7 + ## 1.12.6 ### Patch Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 9cb258b2de..c79dadca05 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs", - "version": "1.12.6", + "version": "1.13.0-next.1", "description": "The Backstage plugin that renders technical documentation for your components", "backstage": { "role": "frontend-plugin", @@ -30,7 +30,7 @@ "sideEffects": false, "exports": { ".": "./src/index.ts", - "./alpha": "./src/alpha.tsx", + "./alpha": "./src/alpha/index.tsx", "./package.json": "./package.json" }, "main": "src/index.ts", @@ -38,7 +38,7 @@ "typesVersions": { "*": { "alpha": [ - "src/alpha.tsx" + "src/alpha/index.tsx" ], "package.json": [ "package.json" diff --git a/plugins/techdocs/report-alpha.api.md b/plugins/techdocs/report-alpha.api.md index e34046ad2e..ce1d10a00c 100644 --- a/plugins/techdocs/report-alpha.api.md +++ b/plugins/techdocs/report-alpha.api.md @@ -14,6 +14,7 @@ import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; +import { IconLinkVerticalProps } from '@backstage/core-components'; import { JSX as JSX_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; import { SearchResultItemExtensionComponent } from '@backstage/plugin-search-react/alpha'; @@ -173,6 +174,45 @@ const _default: FrontendPlugin< filter?: string | EntityPredicate | ((entity: Entity) => boolean); }; }>; + 'entity-icon-link:techdocs/read-docs': ExtensionDefinition<{ + kind: 'entity-icon-link'; + name: 'read-docs'; + config: { + label: string | undefined; + title: string | undefined; + filter: EntityPredicate | undefined; + }; + configInput: { + filter?: EntityPredicate | undefined; + label?: string | undefined; + title?: string | undefined; + }; + output: + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + () => IconLinkVerticalProps, + 'entity-icon-link-props', + {} + >; + inputs: {}; + params: { + useProps: () => Omit; + filter?: EntityPredicate | ((entity: Entity) => boolean); + }; + }>; 'nav-item:techdocs': ExtensionDefinition<{ kind: 'nav-item'; name: undefined; diff --git a/plugins/techdocs/report.api.md b/plugins/techdocs/report.api.md index bb7ff9e5a5..2369466674 100644 --- a/plugins/techdocs/report.api.md +++ b/plugins/techdocs/report.api.md @@ -431,6 +431,7 @@ export const TechDocsReaderPageContent: ( // @public export type TechDocsReaderPageContentProps = { entityRef?: CompoundEntityRef; + defaultPath?: string; withSearch?: boolean; searchResultUrlMapper?: (url: string) => string; onReady?: () => void; diff --git a/plugins/techdocs/src/EntityPageDocs.tsx b/plugins/techdocs/src/EntityPageDocs.tsx index 68c26cd1c3..14dcb00d29 100644 --- a/plugins/techdocs/src/EntityPageDocs.tsx +++ b/plugins/techdocs/src/EntityPageDocs.tsx @@ -20,6 +20,7 @@ import { parseEntityRef, } from '@backstage/catalog-model'; import { TECHDOCS_EXTERNAL_ANNOTATION } from '@backstage/plugin-techdocs-common'; +import { getEntityRootTechDocsPath } from '@backstage/plugin-techdocs-react'; import { TechDocsReaderPage } from './plugin'; import { TechDocsReaderPageContent } from './reader/components/TechDocsReaderPageContent'; @@ -52,12 +53,15 @@ export const EntityPageDocs = ({ } } + const defaultPath = getEntityRootTechDocsPath(entity); + return ( ); diff --git a/plugins/techdocs/src/alpha/hooks/useTechdocsReaderIconLinkProps.tsx b/plugins/techdocs/src/alpha/hooks/useTechdocsReaderIconLinkProps.tsx new file mode 100644 index 0000000000..0cd2cf9ca1 --- /dev/null +++ b/plugins/techdocs/src/alpha/hooks/useTechdocsReaderIconLinkProps.tsx @@ -0,0 +1,51 @@ +/* + * 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 DocsIcon from '@material-ui/icons/Description'; + +import { useRouteRef } from '@backstage/core-plugin-api'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; + +import { + TECHDOCS_ANNOTATION, + TECHDOCS_EXTERNAL_ANNOTATION, +} from '@backstage/plugin-techdocs-common'; +import { buildTechDocsURL } from '@backstage/plugin-techdocs-react'; + +import { useEntity } from '@backstage/plugin-catalog-react'; + +import { techdocsTranslationRef } from '../../translation'; +import { rootDocsRouteRef } from '../../routes'; + +// Note: If you update this hook, please also update the "useTechdocsReaderIconLinkProps" hook +// in the "plugins/catalog/src/components/AboutCard/AboutCard.tsx" file +/** @alpha */ +export function useTechdocsReaderIconLinkProps() { + const { entity } = useEntity(); + const viewTechdocLink = useRouteRef(rootDocsRouteRef); + const { t } = useTranslationRef(techdocsTranslationRef); + + return { + label: t('aboutCard.viewTechdocs'), + disabled: + !( + entity.metadata.annotations?.[TECHDOCS_ANNOTATION] || + entity.metadata.annotations?.[TECHDOCS_EXTERNAL_ANNOTATION] + ) || !viewTechdocLink, + icon: , + href: buildTechDocsURL(entity, viewTechdocLink), + }; +} diff --git a/plugins/techdocs/src/alpha.tsx b/plugins/techdocs/src/alpha/index.tsx similarity index 88% rename from plugins/techdocs/src/alpha.tsx rename to plugins/techdocs/src/alpha/index.tsx index b2bf794e73..44205a480a 100644 --- a/plugins/techdocs/src/alpha.tsx +++ b/plugins/techdocs/src/alpha/index.tsx @@ -35,16 +35,19 @@ import { convertLegacyRouteRef, convertLegacyRouteRefs, } from '@backstage/core-compat-api'; -import { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha'; +import { + EntityContentBlueprint, + EntityIconLinkBlueprint, +} from '@backstage/plugin-catalog-react/alpha'; import { SearchResultListItemBlueprint } from '@backstage/plugin-search-react/alpha'; import { AddonBlueprint } from '@backstage/plugin-techdocs-react/alpha'; -import { TechDocsClient, TechDocsStorageClient } from './client'; +import { TechDocsClient, TechDocsStorageClient } from '../client'; import { rootCatalogDocsRouteRef, rootDocsRouteRef, rootRouteRef, -} from './routes'; -import { TechDocsReaderLayout } from './reader'; +} from '../routes'; +import { TechDocsReaderLayout } from '../reader'; import { attachTechDocsAddonComponentData } from '@backstage/plugin-techdocs-react/alpha'; import { TechDocsAddons, @@ -52,6 +55,16 @@ import { techdocsStorageApiRef, } from '@backstage/plugin-techdocs-react'; +import { useTechdocsReaderIconLinkProps } from './hooks/useTechdocsReaderIconLinkProps'; + +/** @alpha */ +const techdocsEntityIconLink = EntityIconLinkBlueprint.make({ + name: 'read-docs', + params: { + useProps: useTechdocsReaderIconLinkProps, + }, +}); + /** @alpha */ const techDocsStorageApi = ApiBlueprint.make({ name: 'storage', @@ -109,7 +122,7 @@ export const techDocsSearchResultListItemExtension = predicate: result => result.type === 'techdocs', component: async () => { const { TechDocsSearchResultListItem } = await import( - './search/components/TechDocsSearchResultListItem' + '../search/components/TechDocsSearchResultListItem' ); return props => compatWrapper( @@ -130,7 +143,7 @@ const techDocsPage = PageBlueprint.make({ defaultPath: '/docs', routeRef: convertLegacyRouteRef(rootRouteRef), loader: () => - import('./home/components/TechDocsIndexPage').then(m => + import('../home/components/TechDocsIndexPage').then(m => compatWrapper(), ), }, @@ -158,7 +171,7 @@ const techDocsReaderPage = PageBlueprint.makeWithOverrides({ defaultPath: '/docs/:namespace/:kind/:name', routeRef: convertLegacyRouteRef(rootDocsRouteRef), loader: async () => - await import('./Router').then(({ TechDocsReaderRouter }) => { + await import('../Router').then(({ TechDocsReaderRouter }) => { return compatWrapper( @@ -193,7 +206,7 @@ const techDocsEntityContent = EntityContentBlueprint.makeWithOverrides({ defaultTitle: 'TechDocs', routeRef: convertLegacyRouteRef(rootCatalogDocsRouteRef), loader: () => - import('./Router').then(({ EmbeddedDocsRouter }) => { + import('../Router').then(({ EmbeddedDocsRouter }) => { const addons = context.inputs.addons.map(output => { const options = output.get(AddonBlueprint.dataRefs.addon); const Addon = options.component; @@ -236,12 +249,14 @@ const techDocsNavItem = NavItemBlueprint.make({ /** @alpha */ export default createFrontendPlugin({ pluginId: 'techdocs', + info: { packageJson: () => import('../../package.json') }, extensions: [ techDocsClientApi, techDocsStorageApi, techDocsNavItem, techDocsPage, techDocsReaderPage, + techdocsEntityIconLink, techDocsEntityContent, techDocsEntityContentEmptyState, techDocsSearchResultListItemExtension, diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.test.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.test.tsx index a8e7ae7287..77bb3d134f 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.test.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.test.tsx @@ -16,7 +16,10 @@ import { ReactNode } from 'react'; import { waitFor } from '@testing-library/react'; -import { CompoundEntityRef } from '@backstage/catalog-model'; +import { + CompoundEntityRef, + getCompoundEntityRef, +} from '@backstage/catalog-model'; import { techdocsApiRef, TechDocsReaderPageProvider, @@ -121,6 +124,33 @@ describe('', () => { }); }); + it('should render techdocs page content with default path', async () => { + getEntityMetadata.mockResolvedValue(mockEntityMetadata); + getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata); + useTechDocsReaderDom.mockReturnValue(document.createElement('html')); + useReaderState.mockReturnValue({ state: 'cached' }); + + const defaultPath = '/some/path'; + + const rendered = await renderInTestApp( + + + , + ); + + await waitFor(() => { + expect( + rendered.getByTestId('techdocs-native-shadowroot'), + ).toBeInTheDocument(); + }); + + const entityRef = getCompoundEntityRef(mockEntityMetadata); + expect(useTechDocsReaderDom).toHaveBeenCalledWith(entityRef, defaultPath); + }); + it('should not render techdocs content if entity metadata is missing', async () => { getEntityMetadata.mockResolvedValue(undefined); useTechDocsReaderDom.mockReturnValue(document.createElement('html')); diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.tsx index 1db97a82b8..fe72b09073 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.tsx @@ -61,6 +61,11 @@ export type TechDocsReaderPageContentProps = { * @deprecated No need to pass down entityRef as property anymore. Consumes the entityName from `TechDocsReaderPageContext`. Use the {@link @backstage/plugin-techdocs-react#useTechDocsReaderPage} hook for custom reader page content. */ entityRef?: CompoundEntityRef; + /** + * Path in the docs to render by default. This should be used when rendering docs for an entity that specifies the + * "backstage.io/techdocs-entity-path" annotation for deep linking into another entities docs. + */ + defaultPath?: string; /** * Show or hide the search bar, defaults to true. */ @@ -93,7 +98,7 @@ export const TechDocsReaderPageContent = withTechDocsReaderProvider( setShadowRoot, } = useTechDocsReaderPage(); const { state } = useTechDocsReader(); - const dom = useTechDocsReaderDom(entityRef); + const dom = useTechDocsReaderDom(entityRef, props.defaultPath); const path = window.location.pathname; const hash = window.location.hash; const isStyleLoading = useShadowDomStylesLoading(dom); diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx index 9dbb9745d3..0c4d6dee3e 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx @@ -14,7 +14,13 @@ * limitations under the License. */ -import { useCallback, useEffect, useState } from 'react'; +import { + useCallback, + useEffect, + useLayoutEffect, + // useRef, + useState, +} from 'react'; import useMediaQuery from '@material-ui/core/useMediaQuery'; import { useTheme } from '@material-ui/core/styles'; @@ -45,12 +51,30 @@ import { useSanitizerTransformer, useStylesTransformer, handleMetaRedirects, + addNavLinkKeyboardToggle, } from '../../transformers'; import { useNavigateUrl } from './useNavigateUrl'; -import { useParams } from 'react-router-dom'; +import { useLocation, useNavigate, useParams } from 'react-router-dom'; const MOBILE_MEDIA_QUERY = 'screen and (max-width: 76.1875em)'; +// If a defaultPath is specified then we should navigate to that path replacing the +// current location in the history. This should only happen on the initial load so +// navigating to the root of the docs doesn't also redirect. +const useInitialRedirect = (defaultPath?: string) => { + // const hasRun = useRef(false); + + const location = useLocation(); + const navigate = useNavigate(); + const { '*': currPath = '' } = useParams(); + + useLayoutEffect(() => { + if (currPath === '' && defaultPath !== '') { + navigate(`${location.pathname}${defaultPath}`, { replace: true }); + } + }, []); // eslint-disable-line react-hooks/exhaustive-deps +}; + /** * Hook that encapsulates the behavior of getting raw HTML and applying * transforms to it in order to make it function at a basic level in the @@ -58,6 +82,7 @@ const MOBILE_MEDIA_QUERY = 'screen and (max-width: 76.1875em)'; */ export const useTechDocsReaderDom = ( entityRef: CompoundEntityRef, + defaultPath?: string, ): Element | null => { const navigate = useNavigateUrl(); const theme = useTheme(); @@ -76,6 +101,8 @@ export const useTechDocsReaderDom = ( const [dom, setDom] = useState(null); const isStyleLoading = useShadowDomStylesLoading(dom); + useInitialRedirect(defaultPath); + const updateSidebarPositionAndHeight = useCallback(() => { if (!dom) return; @@ -267,6 +294,7 @@ export const useTechDocsReaderDom = ( }, onLoaded: () => {}, }), + addNavLinkKeyboardToggle(), ]), [theme, navigate, analytics, entityRef.name, configApi], ); diff --git a/plugins/techdocs/src/reader/transformers/addNavLinkKeyboardToggle.test.ts b/plugins/techdocs/src/reader/transformers/addNavLinkKeyboardToggle.test.ts new file mode 100644 index 0000000000..3af6151164 --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/addNavLinkKeyboardToggle.test.ts @@ -0,0 +1,77 @@ +/* + * 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 { addNavLinkKeyboardToggle } from './addNavLinkKeyboardToggle'; +import { createTestShadowDom } from '../../test-utils'; +import { act } from '@testing-library/react'; + +describe('addNavLinkKeyboardToggle', () => { + it('adds tabindex and toggles checkbox on Enter/Space', async () => { + let shadowDom: ShadowRoot; + await act(async () => { + shadowDom = await createTestShadowDom( + ` + + + `, + { + preTransformers: [], + postTransformers: [addNavLinkKeyboardToggle()], + }, + ); + }); + const label = shadowDom!.querySelector('label.md-nav__link')!; + const checkbox = shadowDom!.querySelector('#cb1') as HTMLInputElement; + expect(label.getAttribute('tabIndex')).toBe('0'); + + // Simulate keydown: Enter + await act(async () => { + label.dispatchEvent( + new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }), + ); + }); + expect(checkbox.checked).toBe(true); + + // Simulate keydown: space + await act(async () => { + label.dispatchEvent( + new KeyboardEvent('keydown', { key: ' ', bubbles: true }), + ); + }); + expect(checkbox.checked).toBe(false); + }); + + it('does nothing if no for attribute or checkbox', async () => { + let shadowDom: ShadowRoot; + await act(async () => { + shadowDom = await createTestShadowDom( + ``, + { + preTransformers: [], + postTransformers: [addNavLinkKeyboardToggle()], + }, + ); + }); + const label = shadowDom!.querySelector('label.md-nav__link')!; + await act(async () => { + expect(() => + label.dispatchEvent( + new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }), + ), + ).not.toThrow(); + }); + }); +}); diff --git a/plugins/techdocs/src/reader/transformers/addNavLinkKeyboardToggle.ts b/plugins/techdocs/src/reader/transformers/addNavLinkKeyboardToggle.ts new file mode 100644 index 0000000000..9f29df1576 --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/addNavLinkKeyboardToggle.ts @@ -0,0 +1,41 @@ +/* + * 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. + */ + +export function addNavLinkKeyboardToggle() { + return (element: Element) => { + const navLabels = element.querySelectorAll('label.md-nav__link[for]'); + navLabels.forEach(label => { + label.setAttribute('tabIndex', '0'); + label.addEventListener('keydown', event => { + const keyboardEvent = event as KeyboardEvent; + if (keyboardEvent.key === 'Enter' || keyboardEvent.key === ' ') { + const forId = label.getAttribute('for'); + if (!forId) return; + const checkbox = element.querySelector( + `#${forId}`, + ) as HTMLInputElement | null; + if (checkbox && checkbox.type === 'checkbox') { + checkbox.checked = !checkbox.checked; + checkbox.dispatchEvent(new Event('change', { bubbles: true })); + event.preventDefault(); + event.stopPropagation(); + } + } + }); + }); + return element; + }; +} diff --git a/plugins/techdocs/src/reader/transformers/index.ts b/plugins/techdocs/src/reader/transformers/index.ts index 7fdf47b009..571725ed25 100644 --- a/plugins/techdocs/src/reader/transformers/index.ts +++ b/plugins/techdocs/src/reader/transformers/index.ts @@ -28,3 +28,4 @@ export * from './onCssReady'; export * from './scrollIntoNavigation'; export * from './transformer'; export * from './handleMetaRedirects'; +export * from './addNavLinkKeyboardToggle'; diff --git a/plugins/techdocs/src/translation.ts b/plugins/techdocs/src/translation.ts new file mode 100644 index 0000000000..aa62246788 --- /dev/null +++ b/plugins/techdocs/src/translation.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 { createTranslationRef } from '@backstage/core-plugin-api/alpha'; + +/** @alpha */ +export const techdocsTranslationRef = createTranslationRef({ + id: 'techdocs', + messages: { + aboutCard: { + viewTechdocs: 'View TechDocs', + }, + }, +}); diff --git a/plugins/user-settings-backend/CHANGELOG.md b/plugins/user-settings-backend/CHANGELOG.md index 231d1224d3..679a2ef2b8 100644 --- a/plugins/user-settings-backend/CHANGELOG.md +++ b/plugins/user-settings-backend/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-user-settings-backend +## 0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.4-next.1 + - @backstage/backend-defaults@0.10.1-next.1 + - @backstage/backend-plugin-api@1.4.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-node@0.1.21-next.1 + - @backstage/plugin-user-settings-common@0.0.1 + +## 0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.4.0-next.0 + - @backstage/backend-defaults@0.10.1-next.0 + - @backstage/plugin-auth-node@0.6.4-next.0 + - @backstage/plugin-signals-node@0.1.21-next.0 + ## 0.3.2 ### Patch Changes diff --git a/plugins/user-settings-backend/package.json b/plugins/user-settings-backend/package.json index 8d6997afa9..b5e513ede8 100644 --- a/plugins/user-settings-backend/package.json +++ b/plugins/user-settings-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings-backend", - "version": "0.3.2", + "version": "0.3.3-next.1", "description": "The Backstage backend plugin to manage user settings", "backstage": { "role": "backend-plugin", diff --git a/plugins/user-settings/CHANGELOG.md b/plugins/user-settings/CHANGELOG.md index 39072494a5..bc936a1007 100644 --- a/plugins/user-settings/CHANGELOG.md +++ b/plugins/user-settings/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/plugin-user-settings +## 0.8.23-next.1 + +### Patch Changes + +- ab6d3dd: Add some translation to user-setting plugin +- Updated dependencies + - @backstage/plugin-catalog-react@1.18.1-next.1 + - @backstage/catalog-model@1.7.4 + - @backstage/core-app-api@1.17.0 + - @backstage/core-compat-api@0.4.3-next.1 + - @backstage/core-components@0.17.2 + - @backstage/core-plugin-api@1.10.7 + - @backstage/errors@1.2.7 + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/theme@0.6.6 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-react@0.0.13 + - @backstage/plugin-user-settings-common@0.0.1 + +## 0.8.23-next.0 + +### Patch Changes + +- 18c64e9: Added the `info.packageJson` option to the plugin instance for the new frontend system. +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.3-next.0 + - @backstage/core-compat-api@0.4.3-next.0 + - @backstage/plugin-catalog-react@1.18.1-next.0 + ## 0.8.22 ### Patch Changes diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 57c2dc0e7b..2acac5406c 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings", - "version": "0.8.22", + "version": "0.8.23-next.1", "description": "A Backstage plugin that provides a settings page", "backstage": { "role": "frontend-plugin", diff --git a/plugins/user-settings/report-alpha.api.md b/plugins/user-settings/report-alpha.api.md index 9b3b674a10..036ff2ff58 100644 --- a/plugins/user-settings/report-alpha.api.md +++ b/plugins/user-settings/report-alpha.api.md @@ -154,6 +154,8 @@ export const userSettingsTranslationRef: TranslationRef< readonly 'defaultSettingsPage.tabsTitle.general': 'General'; readonly 'settingsLayout.title': 'Settings'; readonly sidebarTitle: 'Settings'; + readonly 'profileCard.title': 'Profile'; + readonly 'appearanceCard.title': 'Appearance'; } >; diff --git a/plugins/user-settings/src/alpha.tsx b/plugins/user-settings/src/alpha.tsx index 228c6a23d2..f351dad40a 100644 --- a/plugins/user-settings/src/alpha.tsx +++ b/plugins/user-settings/src/alpha.tsx @@ -69,6 +69,7 @@ export const settingsNavItem = NavItemBlueprint.make({ */ export default createFrontendPlugin({ pluginId: 'user-settings', + info: { packageJson: () => import('../package.json') }, extensions: [userSettingsPage, settingsNavItem], routes: convertLegacyRouteRefs({ root: settingsRouteRef, diff --git a/plugins/user-settings/src/components/General/UserSettingsAppearanceCard.tsx b/plugins/user-settings/src/components/General/UserSettingsAppearanceCard.tsx index ca7378235d..0fa07eb5fa 100644 --- a/plugins/user-settings/src/components/General/UserSettingsAppearanceCard.tsx +++ b/plugins/user-settings/src/components/General/UserSettingsAppearanceCard.tsx @@ -19,13 +19,16 @@ import List from '@material-ui/core/List'; import { UserSettingsPinToggle } from './UserSettingsPinToggle'; import { UserSettingsThemeToggle } from './UserSettingsThemeToggle'; import { UserSettingsLanguageToggle } from './UserSettingsLanguageToggle'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { userSettingsTranslationRef } from '../../translation'; /** @public */ export const UserSettingsAppearanceCard = () => { const { isMobile } = useSidebarPinState(); + const { t } = useTranslationRef(userSettingsTranslationRef); return ( - + diff --git a/plugins/user-settings/src/components/General/UserSettingsProfileCard.tsx b/plugins/user-settings/src/components/General/UserSettingsProfileCard.tsx index aa9301e379..ac871941dd 100644 --- a/plugins/user-settings/src/components/General/UserSettingsProfileCard.tsx +++ b/plugins/user-settings/src/components/General/UserSettingsProfileCard.tsx @@ -20,13 +20,16 @@ import { UserSettingsSignInAvatar } from './UserSettingsSignInAvatar'; import { UserSettingsMenu } from './UserSettingsMenu'; import { useUserProfile } from '../useUserProfileInfo'; import { InfoCard } from '@backstage/core-components'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { userSettingsTranslationRef } from '../../translation'; /** @public */ export const UserSettingsProfileCard = () => { const { profile, displayName } = useUserProfile(); + const { t } = useTranslationRef(userSettingsTranslationRef); return ( - + diff --git a/plugins/user-settings/src/translation.ts b/plugins/user-settings/src/translation.ts index 9785c4afaa..0161508d14 100644 --- a/plugins/user-settings/src/translation.ts +++ b/plugins/user-settings/src/translation.ts @@ -119,5 +119,11 @@ export const userSettingsTranslationRef = createTranslationRef({ title: 'Settings', }, sidebarTitle: 'Settings', + profileCard: { + title: 'Profile', + }, + appearanceCard: { + title: 'Appearance', + }, }, }); diff --git a/yarn.lock b/yarn.lock index ffcb53f6cd..d8736591ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3636,6 +3636,7 @@ __metadata: yauzl: "npm:^3.0.0" yn: "npm:^4.0.0" zod: "npm:^3.22.4" + zod-to-json-schema: "npm:^3.20.4" peerDependencies: "@google-cloud/cloud-sql-connector": ^1.4.0 peerDependenciesMeta: @@ -3721,7 +3722,26 @@ __metadata: languageName: unknown linkType: soft -"@backstage/backend-plugin-api@npm:^1.0.0, @backstage/backend-plugin-api@npm:^1.1.1, @backstage/backend-plugin-api@workspace:^, @backstage/backend-plugin-api@workspace:packages/backend-plugin-api": +"@backstage/backend-plugin-api@npm:^1.0.0, @backstage/backend-plugin-api@npm:^1.1.1, @backstage/backend-plugin-api@npm:^1.3.1": + version: 1.3.1 + resolution: "@backstage/backend-plugin-api@npm:1.3.1" + dependencies: + "@backstage/cli-common": "npm:^0.1.15" + "@backstage/config": "npm:^1.3.2" + "@backstage/errors": "npm:^1.2.7" + "@backstage/plugin-auth-node": "npm:^0.6.3" + "@backstage/plugin-permission-common": "npm:^0.9.0" + "@backstage/plugin-permission-node": "npm:^0.10.0" + "@backstage/types": "npm:^1.2.1" + "@types/express": "npm:^4.17.6" + "@types/luxon": "npm:^3.0.0" + knex: "npm:^3.0.0" + luxon: "npm:^3.0.0" + checksum: 10/c78a0cd73c9e2ab3f611e62b1570b83fd496f501616caf00877c5191a7c431671f66f77d6e7fd2a35982915f8cb1b69a85cf7d26e3f236a28f709a6ae7af8eaf + languageName: node + linkType: hard + +"@backstage/backend-plugin-api@workspace:^, @backstage/backend-plugin-api@workspace:packages/backend-plugin-api": version: 0.0.0-use.local resolution: "@backstage/backend-plugin-api@workspace:packages/backend-plugin-api" dependencies: @@ -3735,9 +3755,12 @@ __metadata: "@backstage/plugin-permission-node": "workspace:^" "@backstage/types": "workspace:^" "@types/express": "npm:^4.17.6" + "@types/json-schema": "npm:^7.0.6" "@types/luxon": "npm:^3.0.0" + json-schema: "npm:^0.4.0" knex: "npm:^3.0.0" luxon: "npm:^3.0.0" + zod: "npm:^3.22.4" languageName: unknown linkType: soft @@ -3821,7 +3844,19 @@ __metadata: languageName: unknown linkType: soft -"@backstage/catalog-client@npm:^1.9.1, @backstage/catalog-client@workspace:^, @backstage/catalog-client@workspace:packages/catalog-client": +"@backstage/catalog-client@npm:^1.10.0, @backstage/catalog-client@npm:^1.9.1": + version: 1.10.0 + resolution: "@backstage/catalog-client@npm:1.10.0" + dependencies: + "@backstage/catalog-model": "npm:^1.7.4" + "@backstage/errors": "npm:^1.2.7" + cross-fetch: "npm:^4.0.0" + uri-template: "npm:^2.0.0" + checksum: 10/565512931ef340c272ebbffcac7c5f54348ed67a73be80cdecb8768c547a1da41c72b02c0830f3036a06edbc8c77b9720ee5f1f498b2308f799fd409bb4bbbe1 + languageName: node + linkType: hard + +"@backstage/catalog-client@workspace:^, @backstage/catalog-client@workspace:packages/catalog-client": version: 0.0.0-use.local resolution: "@backstage/catalog-client@workspace:packages/catalog-client" dependencies: @@ -3834,7 +3869,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/catalog-model@npm:^1.4.3, @backstage/catalog-model@npm:^1.7.3, @backstage/catalog-model@workspace:^, @backstage/catalog-model@workspace:packages/catalog-model": +"@backstage/catalog-model@npm:^1.4.3, @backstage/catalog-model@npm:^1.7.3, @backstage/catalog-model@npm:^1.7.4, @backstage/catalog-model@workspace:^, @backstage/catalog-model@workspace:packages/catalog-model": version: 0.0.0-use.local resolution: "@backstage/catalog-model@workspace:packages/catalog-model" dependencies: @@ -3849,7 +3884,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/cli-common@npm:^0.1.14, @backstage/cli-common@workspace:^, @backstage/cli-common@workspace:packages/cli-common": +"@backstage/cli-common@npm:^0.1.14, @backstage/cli-common@npm:^0.1.15, @backstage/cli-common@workspace:^, @backstage/cli-common@workspace:packages/cli-common": version: 0.0.0-use.local resolution: "@backstage/cli-common@workspace:packages/cli-common" dependencies: @@ -4104,7 +4139,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/core-app-api@workspace:^, @backstage/core-app-api@workspace:packages/core-app-api": +"@backstage/core-app-api@npm:^1.17.0, @backstage/core-app-api@workspace:^, @backstage/core-app-api@workspace:packages/core-app-api": version: 0.0.0-use.local resolution: "@backstage/core-app-api@workspace:packages/core-app-api" dependencies: @@ -4148,6 +4183,27 @@ __metadata: languageName: unknown linkType: soft +"@backstage/core-compat-api@npm:^0.4.2": + version: 0.4.2 + resolution: "@backstage/core-compat-api@npm:0.4.2" + dependencies: + "@backstage/core-plugin-api": "npm:^1.10.7" + "@backstage/frontend-plugin-api": "npm:^0.10.2" + "@backstage/plugin-catalog-react": "npm:^1.18.0" + "@backstage/version-bridge": "npm:^1.0.11" + lodash: "npm:^4.17.21" + 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 + checksum: 10/7e93c3ff7a9979ee8cb0ab582e66bd0febb5995c91cc0545473e9e86fc75c1ebbb0a24a901e120d7e4ecf122bbb31de1b4f8fc78c7da5f6d84daa9d6b9194035 + languageName: node + linkType: hard + "@backstage/core-compat-api@workspace:^, @backstage/core-compat-api@workspace:packages/core-compat-api": version: 0.0.0-use.local resolution: "@backstage/core-compat-api@workspace:packages/core-compat-api" @@ -4288,7 +4344,7 @@ __metadata: languageName: node linkType: hard -"@backstage/core-components@npm:^0.17.1, @backstage/core-components@workspace:^, @backstage/core-components@workspace:packages/core-components": +"@backstage/core-components@npm:^0.17.1, @backstage/core-components@npm:^0.17.2, @backstage/core-components@workspace:^, @backstage/core-components@workspace:packages/core-components": version: 0.0.0-use.local resolution: "@backstage/core-components@workspace:packages/core-components" dependencies: @@ -4366,7 +4422,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/core-plugin-api@npm:^1.10.0, @backstage/core-plugin-api@npm:^1.10.6, @backstage/core-plugin-api@npm:^1.8.2, @backstage/core-plugin-api@workspace:^, @backstage/core-plugin-api@workspace:packages/core-plugin-api": +"@backstage/core-plugin-api@npm:^1.10.0, @backstage/core-plugin-api@npm:^1.10.6, @backstage/core-plugin-api@npm:^1.10.7, @backstage/core-plugin-api@npm:^1.8.2, @backstage/core-plugin-api@workspace:^, @backstage/core-plugin-api@workspace:packages/core-plugin-api": version: 0.0.0-use.local resolution: "@backstage/core-plugin-api@workspace:packages/core-plugin-api" dependencies: @@ -4499,6 +4555,32 @@ __metadata: languageName: unknown linkType: soft +"@backstage/frontend-app-api@npm:^0.11.2": + version: 0.11.2 + resolution: "@backstage/frontend-app-api@npm:0.11.2" + dependencies: + "@backstage/config": "npm:^1.3.2" + "@backstage/core-app-api": "npm:^1.17.0" + "@backstage/core-plugin-api": "npm:^1.10.7" + "@backstage/errors": "npm:^1.2.7" + "@backstage/frontend-defaults": "npm:^0.2.2" + "@backstage/frontend-plugin-api": "npm:^0.10.2" + "@backstage/types": "npm:^1.2.1" + "@backstage/version-bridge": "npm:^1.0.11" + lodash: "npm:^4.17.21" + zod: "npm:^3.22.4" + 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 + checksum: 10/06cf128df60b0322c07c14853146da5096625d58a344555571a65ead221f03dc1f4e2fe7da364b2afec010be13a4d4a4574060a3f65c023bdcdc93d20d09c33d + languageName: node + linkType: hard + "@backstage/frontend-app-api@workspace:^, @backstage/frontend-app-api@workspace:packages/frontend-app-api": version: 0.0.0-use.local resolution: "@backstage/frontend-app-api@workspace:packages/frontend-app-api" @@ -4533,6 +4615,28 @@ __metadata: languageName: unknown linkType: soft +"@backstage/frontend-defaults@npm:^0.2.2": + version: 0.2.2 + resolution: "@backstage/frontend-defaults@npm:0.2.2" + dependencies: + "@backstage/config": "npm:^1.3.2" + "@backstage/errors": "npm:^1.2.7" + "@backstage/frontend-app-api": "npm:^0.11.2" + "@backstage/frontend-plugin-api": "npm:^0.10.2" + "@backstage/plugin-app": "npm:^0.1.9" + "@react-hookz/web": "npm:^24.0.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 + checksum: 10/23e6156314cb80d9cc1128ee3ad2d3afee3e2a6e88c049853b19133fc95e75f4361e735f26903e44fa62b29d39df5381c53200799a8cc3b3fd0943682a5e51f5 + languageName: node + linkType: hard + "@backstage/frontend-defaults@workspace:^, @backstage/frontend-defaults@workspace:packages/frontend-defaults": version: 0.0.0-use.local resolution: "@backstage/frontend-defaults@workspace:packages/frontend-defaults" @@ -4594,6 +4698,30 @@ __metadata: languageName: unknown linkType: soft +"@backstage/frontend-plugin-api@npm:^0.10.2": + version: 0.10.2 + resolution: "@backstage/frontend-plugin-api@npm:0.10.2" + dependencies: + "@backstage/core-components": "npm:^0.17.2" + "@backstage/core-plugin-api": "npm:^1.10.7" + "@backstage/types": "npm:^1.2.1" + "@backstage/version-bridge": "npm:^1.0.11" + "@material-ui/core": "npm:^4.12.4" + lodash: "npm:^4.17.21" + zod: "npm:^3.22.4" + zod-to-json-schema: "npm:^3.21.4" + 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 + checksum: 10/4f01cf2ae8352dd8b5f7ff66a97739c6f9f8b19b182ae7cb05db542d02fa0cdee805712c0d355068de3c7cd851404fe6e424c5b7a0ffbcec686c42bc78030510 + languageName: node + linkType: hard + "@backstage/frontend-plugin-api@workspace:^, @backstage/frontend-plugin-api@workspace:packages/frontend-plugin-api": version: 0.0.0-use.local resolution: "@backstage/frontend-plugin-api@workspace:packages/frontend-plugin-api" @@ -4628,6 +4756,31 @@ __metadata: languageName: unknown linkType: soft +"@backstage/frontend-test-utils@npm:^0.3.2": + version: 0.3.2 + resolution: "@backstage/frontend-test-utils@npm:0.3.2" + dependencies: + "@backstage/config": "npm:^1.3.2" + "@backstage/frontend-app-api": "npm:^0.11.2" + "@backstage/frontend-plugin-api": "npm:^0.10.2" + "@backstage/plugin-app": "npm:^0.1.9" + "@backstage/test-utils": "npm:^1.7.8" + "@backstage/types": "npm:^1.2.1" + "@backstage/version-bridge": "npm:^1.0.11" + zod: "npm:^3.22.4" + peerDependencies: + "@testing-library/react": ^16.0.0 + "@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 + checksum: 10/3b33d0165e2bda8460e433649fec04d7594c7d987b7cdc59b46a955208266481d15df0f6d529a0bfbece7050d6510c0e320bf77fc81296579747e9a469d8e7d0 + languageName: node + linkType: hard + "@backstage/frontend-test-utils@workspace:^, @backstage/frontend-test-utils@workspace:packages/frontend-test-utils": version: 0.0.0-use.local resolution: "@backstage/frontend-test-utils@workspace:packages/frontend-test-utils" @@ -4677,7 +4830,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/integration-react@npm:^1.1.24, @backstage/integration-react@workspace:^, @backstage/integration-react@workspace:packages/integration-react": +"@backstage/integration-react@npm:^1.1.24, @backstage/integration-react@npm:^1.2.7, @backstage/integration-react@workspace:^, @backstage/integration-react@workspace:packages/integration-react": version: 0.0.0-use.local resolution: "@backstage/integration-react@workspace:packages/integration-react" dependencies: @@ -4869,6 +5022,33 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-app@npm:^0.1.9": + version: 0.1.9 + resolution: "@backstage/plugin-app@npm:0.1.9" + dependencies: + "@backstage/core-components": "npm:^0.17.2" + "@backstage/core-plugin-api": "npm:^1.10.7" + "@backstage/frontend-plugin-api": "npm:^0.10.2" + "@backstage/integration-react": "npm:^1.2.7" + "@backstage/plugin-permission-react": "npm:^0.4.34" + "@backstage/theme": "npm:^0.6.6" + "@backstage/types": "npm:^1.2.1" + "@material-ui/core": "npm:^4.9.13" + "@material-ui/icons": "npm:^4.9.1" + "@material-ui/lab": "npm:^4.0.0-alpha.61" + react-use: "npm:^17.2.4" + 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 + checksum: 10/9c357605a8427f8bf5099968c612f90015d34d757a9494ea22d487bec20593cd80a7a50326e46344fc877ed163a14f2675d33530c1d756411316b0d373096aab + languageName: node + linkType: hard + "@backstage/plugin-app@workspace:^, @backstage/plugin-app@workspace:plugins/app": version: 0.0.0-use.local resolution: "@backstage/plugin-app@workspace:plugins/app" @@ -5356,6 +5536,29 @@ __metadata: languageName: node linkType: hard +"@backstage/plugin-auth-node@npm:^0.6.3": + version: 0.6.3 + resolution: "@backstage/plugin-auth-node@npm:0.6.3" + dependencies: + "@backstage/backend-plugin-api": "npm:^1.3.1" + "@backstage/catalog-client": "npm:^1.10.0" + "@backstage/catalog-model": "npm:^1.7.4" + "@backstage/config": "npm:^1.3.2" + "@backstage/errors": "npm:^1.2.7" + "@backstage/types": "npm:^1.2.1" + "@types/express": "npm:^4.17.6" + "@types/passport": "npm:^1.0.3" + express: "npm:^4.17.1" + jose: "npm:^5.0.0" + lodash: "npm:^4.17.21" + passport: "npm:^0.7.0" + zod: "npm:^3.22.4" + zod-to-json-schema: "npm:^3.21.4" + zod-validation-error: "npm:^3.4.0" + checksum: 10/7f7fd4c1072512c8db6902e7a3e75319f73e2e487da0d202f3fde446eb340a34e7b1c0ce92d16f4dc2a5525aecd1df78979cdcfc650a48a41b2441f221fc0579 + languageName: node + linkType: hard + "@backstage/plugin-auth-node@workspace:^, @backstage/plugin-auth-node@workspace:plugins/auth-node": version: 0.0.0-use.local resolution: "@backstage/plugin-auth-node@workspace:plugins/auth-node" @@ -5889,7 +6092,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-catalog-common@npm:^1.0.20, @backstage/plugin-catalog-common@workspace:^, @backstage/plugin-catalog-common@workspace:plugins/catalog-common": +"@backstage/plugin-catalog-common@npm:^1.0.20, @backstage/plugin-catalog-common@npm:^1.1.4, @backstage/plugin-catalog-common@workspace:^, @backstage/plugin-catalog-common@workspace:plugins/catalog-common": version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-common@workspace:plugins/catalog-common" dependencies: @@ -6015,7 +6218,48 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-catalog-react@npm:^1.17.0, @backstage/plugin-catalog-react@npm:^1.9.3, @backstage/plugin-catalog-react@workspace:^, @backstage/plugin-catalog-react@workspace:plugins/catalog-react": +"@backstage/plugin-catalog-react@npm:^1.17.0, @backstage/plugin-catalog-react@npm:^1.18.0, @backstage/plugin-catalog-react@npm:^1.9.3": + version: 1.18.0 + resolution: "@backstage/plugin-catalog-react@npm:1.18.0" + dependencies: + "@backstage/catalog-client": "npm:^1.10.0" + "@backstage/catalog-model": "npm:^1.7.4" + "@backstage/core-compat-api": "npm:^0.4.2" + "@backstage/core-components": "npm:^0.17.2" + "@backstage/core-plugin-api": "npm:^1.10.7" + "@backstage/errors": "npm:^1.2.7" + "@backstage/frontend-plugin-api": "npm:^0.10.2" + "@backstage/frontend-test-utils": "npm:^0.3.2" + "@backstage/integration-react": "npm:^1.2.7" + "@backstage/plugin-catalog-common": "npm:^1.1.4" + "@backstage/plugin-permission-common": "npm:^0.9.0" + "@backstage/plugin-permission-react": "npm:^0.4.34" + "@backstage/types": "npm:^1.2.1" + "@backstage/version-bridge": "npm:^1.0.11" + "@material-ui/core": "npm:^4.12.2" + "@material-ui/icons": "npm:^4.9.1" + "@material-ui/lab": "npm:4.0.0-alpha.61" + "@react-hookz/web": "npm:^24.0.0" + classnames: "npm:^2.2.6" + lodash: "npm:^4.17.21" + material-ui-popup-state: "npm:^1.9.3" + qs: "npm:^6.9.4" + react-use: "npm:^17.2.4" + yaml: "npm:^2.0.0" + zen-observable: "npm:^0.10.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 + checksum: 10/1dec452c7bb1bd3538839a3a99943f5a5b50e5f414e31c679461e4810e5ea0e623cd41de6f08922fe577f3855459903f999c0ee0635982e1dd6bffbb46eceb08 + languageName: node + linkType: hard + +"@backstage/plugin-catalog-react@workspace:^, @backstage/plugin-catalog-react@workspace:plugins/catalog-react": version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-react@workspace:plugins/catalog-react" dependencies: @@ -6136,6 +6380,8 @@ __metadata: "@backstage/plugin-scaffolder-common": "workspace:^" "@backstage/plugin-search-common": "workspace:^" "@backstage/plugin-search-react": "workspace:^" + "@backstage/plugin-techdocs-common": "workspace:^" + "@backstage/plugin-techdocs-react": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/types": "workspace:^" "@backstage/version-bridge": "workspace:^" @@ -6836,7 +7082,6 @@ __metadata: "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" - "@backstage/catalog-client": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" @@ -7061,7 +7306,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-permission-common@workspace:^, @backstage/plugin-permission-common@workspace:plugins/permission-common": +"@backstage/plugin-permission-common@npm:^0.9.0, @backstage/plugin-permission-common@workspace:^, @backstage/plugin-permission-common@workspace:plugins/permission-common": version: 0.0.0-use.local resolution: "@backstage/plugin-permission-common@workspace:plugins/permission-common" dependencies: @@ -7077,6 +7322,24 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-permission-node@npm:^0.10.0": + version: 0.10.0 + resolution: "@backstage/plugin-permission-node@npm:0.10.0" + dependencies: + "@backstage/backend-plugin-api": "npm:^1.3.1" + "@backstage/config": "npm:^1.3.2" + "@backstage/errors": "npm:^1.2.7" + "@backstage/plugin-auth-node": "npm:^0.6.3" + "@backstage/plugin-permission-common": "npm:^0.9.0" + "@types/express": "npm:^4.17.6" + express: "npm:^4.17.1" + express-promise-router: "npm:^4.1.0" + zod: "npm:^3.22.4" + zod-to-json-schema: "npm:^3.20.4" + checksum: 10/ffd6efc8625c36c5afbde08cf31aeac56a64c3b05547c8b2ba77b6301044652d2c5c4145f8445694ec8b1155173100472f21df1a4ec80b95d454485feb35231b + languageName: node + linkType: hard + "@backstage/plugin-permission-node@workspace:^, @backstage/plugin-permission-node@workspace:plugins/permission-node": version: 0.0.0-use.local resolution: "@backstage/plugin-permission-node@workspace:plugins/permission-node" @@ -7100,7 +7363,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-permission-react@workspace:^, @backstage/plugin-permission-react@workspace:plugins/permission-react": +"@backstage/plugin-permission-react@npm:^0.4.34, @backstage/plugin-permission-react@workspace:^, @backstage/plugin-permission-react@workspace:plugins/permission-react": version: 0.0.0-use.local resolution: "@backstage/plugin-permission-react@workspace:plugins/permission-react" dependencies: @@ -7195,6 +7458,7 @@ __metadata: fs-extra: "npm:^11.2.0" msw: "npm:^1.0.0" yaml: "npm:^2.0.0" + zod: "npm:^3.22.4" languageName: unknown linkType: soft @@ -7213,6 +7477,7 @@ __metadata: fs-extra: "npm:^11.2.0" msw: "npm:^1.0.0" yaml: "npm:^2.0.0" + zod: "npm:^3.22.4" languageName: unknown linkType: soft @@ -7336,12 +7601,12 @@ __metadata: dependencies: "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" - "@backstage/catalog-client": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" + "@backstage/plugin-catalog-node": "workspace:^" "@backstage/plugin-scaffolder-node": "workspace:^" "@backstage/plugin-scaffolder-node-test-utils": "workspace:^" "@backstage/types": "workspace:^" @@ -7353,6 +7618,7 @@ __metadata: octokit: "npm:^3.0.0" octokit-plugin-create-pull-request: "npm:^5.0.0" yaml: "npm:^2.0.0" + zod: "npm:^3.22.4" languageName: unknown linkType: soft @@ -7387,7 +7653,6 @@ __metadata: "@backstage/plugin-notifications-node": "workspace:^" "@backstage/plugin-scaffolder-node": "workspace:^" "@backstage/plugin-scaffolder-node-test-utils": "workspace:^" - octokit: "npm:^3.0.0" yaml: "npm:^2.0.0" languageName: unknown linkType: soft @@ -7456,7 +7721,6 @@ __metadata: "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" - "@backstage/catalog-client": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" @@ -8266,6 +8530,7 @@ __metadata: "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" + "@backstage/plugin-techdocs-common": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/theme": "workspace:^" "@backstage/version-bridge": "workspace:^" @@ -8473,6 +8738,7 @@ __metadata: commander: "npm:^12.0.0" fs-extra: "npm:^11.2.0" glob: "npm:^8.0.3" + globby: "npm:^11.0.0" is-glob: "npm:^4.0.3" js-yaml: "npm:^4.1.0" just-diff: "npm:^6.0.2" @@ -8487,6 +8753,7 @@ __metadata: ts-morph: "npm:^24.0.0" typedoc: "npm:^0.28.0" yaml-diff-patch: "npm:^2.0.0" + zod: "npm:^3.22.4" peerDependencies: "@microsoft/api-extractor-model": "*" "@microsoft/tsdoc": "*" @@ -8505,7 +8772,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/test-utils@workspace:^, @backstage/test-utils@workspace:packages/test-utils": +"@backstage/test-utils@npm:^1.7.8, @backstage/test-utils@workspace:^, @backstage/test-utils@workspace:packages/test-utils": version: 0.0.0-use.local resolution: "@backstage/test-utils@workspace:packages/test-utils" dependencies: @@ -8557,7 +8824,7 @@ __metadata: languageName: node linkType: hard -"@backstage/theme@npm:^0.6.0, @backstage/theme@workspace:^, @backstage/theme@workspace:packages/theme": +"@backstage/theme@npm:^0.6.0, @backstage/theme@npm:^0.6.6, @backstage/theme@workspace:^, @backstage/theme@workspace:packages/theme": version: 0.0.0-use.local resolution: "@backstage/theme@workspace:packages/theme" dependencies: @@ -8595,7 +8862,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/version-bridge@npm:^1.0.10, @backstage/version-bridge@npm:^1.0.7, @backstage/version-bridge@workspace:^, @backstage/version-bridge@workspace:packages/version-bridge": +"@backstage/version-bridge@npm:^1.0.10, @backstage/version-bridge@npm:^1.0.11, @backstage/version-bridge@npm:^1.0.7, @backstage/version-bridge@workspace:^, @backstage/version-bridge@workspace:packages/version-bridge": version: 0.0.0-use.local resolution: "@backstage/version-bridge@workspace:packages/version-bridge" dependencies: @@ -48920,9 +49187,9 @@ __metadata: linkType: hard "zod@npm:^3.22.4": - version: 3.23.8 - resolution: "zod@npm:3.23.8" - checksum: 10/846fd73e1af0def79c19d510ea9e4a795544a67d5b34b7e1c4d0425bf6bfd1c719446d94cdfa1721c1987d891321d61f779e8236fde517dc0e524aa851a6eff1 + version: 3.24.4 + resolution: "zod@npm:3.24.4" + checksum: 10/3d545792fa54bb27ee5dbc34a5709e81f603185fcc94c8204b5d95c20dc4c81d870ff9c51f3884a30ef05cdc601449f4c4df254ac4783f0827b1faed7c1cdb48 languageName: node linkType: hard